Java實(shí)戰(zhàn)之多線程模擬站點(diǎn)售票
一、實(shí)驗(yàn)題目

二、分析
哦吼,這次的實(shí)驗(yàn)題目是一道非常經(jīng)典的多線程買(mǎi)票問(wèn)題。題目要求我們創(chuàng)建5個(gè)線程來(lái)模擬賣(mài)票,當(dāng)然這其中就包含多線程存在也就是我們要解決的問(wèn)題,重復(fù)賣(mài)票和超額賣(mài)票。即多個(gè)窗口賣(mài)出同一張票以及窗口賣(mài)出非正數(shù)編號(hào)的票。
不過(guò)這個(gè)問(wèn)題可以先放一下,我們先來(lái)創(chuàng)建基礎(chǔ)的線程模型,并在主方法中創(chuàng)建五個(gè)線程讓他們跑起來(lái);
話(huà)不多說(shuō),上代碼。
public class Ticket {
public static void main(String[] args) {
for(int i = 1;i <= 5;i++) {
//創(chuàng)建5個(gè)線程并啟動(dòng)他們
//注意一定要使用Thread類(lèi)創(chuàng)建線程并使用start方法啟動(dòng)
//而不是直接創(chuàng)建TicketSeller對(duì)象調(diào)用run方法!!!!!!
new Thread(new TicketSeller(i)).start();
}
}
}
//售票類(lèi),實(shí)現(xiàn)Runnable接口,可以作為線程執(zhí)行對(duì)象
class TicketSeller implements Runnable{
//該售票窗口編號(hào)
private int code;
public TicketSeller(int code) {
this.code = code;
}
@Override
public void run() {
for(int i = 0;i < 5;i++) {
System.out.println(code + "號(hào)窗口");
//為了使線程能夠交替執(zhí)行,打印完成語(yǔ)句讓線程休眠一小會(huì)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
代碼的含義和需要注意的點(diǎn)都在注釋里面了,一定要看注釋?zhuān)。。?/strong>
運(yùn)行結(jié)果就是:

后面太長(zhǎng)了就不放了。。。。
完成了基礎(chǔ)的多線程框架搭建后,我們來(lái)為每個(gè)線程執(zhí)行過(guò)程中加入賣(mài)票的程序
首先要解決的一個(gè)問(wèn)題是:票存在哪里?。毋庸置疑的是由于是多線程并發(fā)的售票,因此票這個(gè)變量一定是被多個(gè)線程所共享的,而不能是每個(gè)線程對(duì)象自己的屬性。
一個(gè)可行的方案是在TicketSellet類(lèi)中定義靜態(tài)的票計(jì)數(shù),這樣所有的線程訪問(wèn)票的時(shí)候訪問(wèn)的都是同一個(gè)票計(jì)數(shù)變量。
另一個(gè)可行方案是使用一個(gè)對(duì)象管理票,票計(jì)數(shù)是這個(gè)對(duì)象的成員,并且讓每個(gè)TicketSeller持有相同的對(duì)象。那么多個(gè)線程也同樣共享票計(jì)數(shù)。
當(dāng)然,可行的方案還有很多,現(xiàn)在我們先來(lái)實(shí)現(xiàn)第一種,在之后的改進(jìn)中,我們還會(huì)用到第二種。
先來(lái)一個(gè)沒(méi)有加鎖的寫(xiě)法,看看他的問(wèn)題
//售票類(lèi),實(shí)現(xiàn)Runnable接口,可以作為線程執(zhí)行對(duì)象
class TicketSeller implements Runnable{
//票數(shù)
private static int tickets = 100;
//該售票窗口編號(hào)
private int code;
public TicketSeller(int code) {
this.code = code;
}
@Override
public void run() {
//如果有票就一直賣(mài)
while(tickets > 0) {
System.out.println(code + "_____" + tickets--);
//賣(mài)過(guò)票之后休眠一小會(huì)等待其他線程操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
這段不加鎖的代碼會(huì)遇到許多很尷尬的問(wèn)題,首先一個(gè),多線程之間的重復(fù)賣(mài)票:

除了重復(fù)賣(mài)票,還有超額賣(mài)票的行為:

這當(dāng)然是不能容忍的,解決辦法是在賣(mài)票過(guò)程對(duì)tickets變量加鎖,使得每次只能有一個(gè)線程進(jìn)入賣(mài)票的環(huán)節(jié)而其他線程只能循環(huán)等待:


但是這樣處理并不能完全結(jié)局上面的問(wèn)題,盡管每次只能一個(gè)線程進(jìn)入賣(mài)票階段阻止了重復(fù)賣(mài)票。但是超額賣(mài)票的行為依舊會(huì)發(fā)生:

好嘛,這次非常嚴(yán)重
原因嗎其實(shí)并不復(fù)雜,我們加鎖只是能阻止多個(gè)進(jìn)程進(jìn)入賣(mài)票程序,但是會(huì)有其他程序達(dá)成判斷條件,執(zhí)行到賣(mài)票程序之前等待進(jìn)入,如果一個(gè)線程將票賣(mài)完而此時(shí)有其他程序剛好等待進(jìn)入,那么就會(huì)出現(xiàn)上面的情況。
所以我們還需要加上一道保險(xiǎn):

經(jīng)過(guò)這樣的處理,票子就可以放心的賣(mài)出而不用擔(dān)心重或者賣(mài)超了
三、完整代碼:
public class Ticket {
public static void main(String[] args) {
for(int i = 1;i <= 5;i++) {
//創(chuàng)建5個(gè)線程并啟動(dòng)他們
//注意一定要使用Thread類(lèi)創(chuàng)建線程并使用start方法啟動(dòng)
//而不是直接創(chuàng)建TicketSeller對(duì)象調(diào)用run方法!!!!!!
new Thread(new TicketSeller(i)).start();
}
}
}
//售票類(lèi),實(shí)現(xiàn)Runnable接口,可以作為線程執(zhí)行對(duì)象
class TicketSeller implements Runnable{
//票數(shù)
private static int tickets = 100;
//同步鎖
private static Object lock = new Object();
//該售票窗口編號(hào)
private int code;
public TicketSeller(int code) {
this.code = code;
}
@Override
public void run() {
//如果有票就一直賣(mài)
while(tickets > 0) {
synchronized (lock) {
//如果票賣(mài)完了則跳出
if(tickets <= 0) {
break;
}
System.out.println(code + "_____" + tickets--);
//賣(mài)過(guò)票之后休眠一小會(huì)等待其他線程操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
在前面我們還提出了另一種方案,就是使用一個(gè)對(duì)象管理票的售賣(mài)。這種方案就不展開(kāi)啰嗦了,直接上代碼:
public class Ticket {
public static void main(String[] args) {
//創(chuàng)建一個(gè)票管理對(duì)象,票數(shù)為100
TicketSet ts = new TicketSet(100);
//創(chuàng)建5個(gè)線程,使用同一個(gè)票管理對(duì)象
for(int i = 1;i <= 5;i++) {
new Thread(new TicketSeller(ts, i)).start();
}
}
}
//票管理類(lèi)
class TicketSet{
//票數(shù)
private int tickets;
public TicketSet(int tickets) {
this.tickets = tickets;
}
private boolean hasTicket() {
return tickets > 0;
}
//售票方法,使用同步鎖,每次只能有一個(gè)線程訪問(wèn)該方法
//返回結(jié)果為是否賣(mài)出去票
synchronized public boolean sellTicket(int code) {
if(hasTicket()) {
System.out.println(code + "_____" + tickets--);
return true;
}else {
return false;
}
}
}
//售票類(lèi)
class TicketSeller implements Runnable{
//票管理對(duì)象
private TicketSet ts;
private int code;
public TicketSeller(TicketSet ts,int code) {
this.ts = ts;
this.code = code;
}
@Override
public void run() {
//嘗試調(diào)用票管理的售票方法,售票成功后休眠一小會(huì)
while(ts.sellTicket(code)){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
到此這篇關(guān)于Java實(shí)戰(zhàn)之多線程模擬站點(diǎn)售票的文章就介紹到這了,更多相關(guān)多線程模擬站點(diǎn)售票內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis自動(dòng)建表的實(shí)現(xiàn)方法
這篇文章主要介紹了mybatis自動(dòng)建表的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
詳解SpringBoot如何刪除引用jar包中的無(wú)用bean
為了趕速度和直接將之前多模塊的maven項(xiàng)目中的部分模塊,直接以jar包的形式引入到新項(xiàng)目中了,雖然省去了不少開(kāi)發(fā)時(shí)間,導(dǎo)致項(xiàng)目臃腫,啟動(dòng)很慢。本文將用@ComponentScan注解去實(shí)現(xiàn)讓項(xiàng)目只加載自己需要的bean,需要的可以參考一下2022-06-06
SpringBoot創(chuàng)建RSocket服務(wù)器的全過(guò)程記錄
RSocket應(yīng)用層協(xié)議支持 Reactive Streams語(yǔ)義, 例如:用RSocket作為HTTP的一種替代方案。這篇文章主要給大家介紹了關(guān)于SpringBoot創(chuàng)建RSocket服務(wù)器的相關(guān)資料,需要的朋友可以參考下2021-05-05
Mybatis中傳遞多個(gè)參數(shù)的4種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于Mybatis中傳遞多個(gè)參數(shù)的4種方法,并且介紹了關(guān)于使用Mapper接口時(shí)參數(shù)傳遞方式,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04
Spring?Boot實(shí)現(xiàn)WebSocket實(shí)時(shí)通信
本文主要介紹了Spring?Boot實(shí)現(xiàn)WebSocket實(shí)時(shí)通信,包含實(shí)現(xiàn)實(shí)時(shí)消息傳遞和群發(fā)消息等功能,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05
SpringBoot配置類(lèi)編寫(xiě)過(guò)程圖解
這篇文章主要介紹了SpringBoot配置類(lèi)編寫(xiě)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

