- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- Java實(shí)戰之多線(xiàn)程模擬站點(diǎn)售票
哦吼,這次的實(shí)驗題目是一道非常經(jīng)典的多線(xiàn)程買(mǎi)票問(wèn)題。題目要求我們創(chuàng )建5個(gè)線(xiàn)程來(lái)模擬賣(mài)票,當然這其中就包含多線(xiàn)程存在也就是我們要解決的問(wèn)題,重復賣(mài)票和超額賣(mài)票。即多個(gè)窗口賣(mài)出同一張票以及窗口賣(mài)出非正數編號的票。
不過(guò)這個(gè)問(wèn)題可以先放一下,我們先來(lái)創(chuàng )建基礎的線(xiàn)程模型,并在主方法中創(chuàng )建五個(gè)線(xiàn)程讓他們跑起來(lái);
話(huà)不多說(shuō),上代碼。
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創(chuàng )建5個(gè)線(xiàn)程并啟動(dòng)他們 //注意一定要使用Thread類(lèi)創(chuàng )建線(xiàn)程并使用start方法啟動(dòng) //而不是直接創(chuàng )建TicketSeller對象調用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類(lèi),實(shí)現Runnable接口,可以作為線(xiàn)程執行對象 class TicketSeller implements Runnable{ //該售票窗口編號 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 + "號窗口"); //為了使線(xiàn)程能夠交替執行,打印完成語(yǔ)句讓線(xiàn)程休眠一小會(huì ) try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }
代碼的含義和需要注意的點(diǎn)都在注釋里面了,一定要看注釋?zhuān)。。?/strong>
運行結果就是:
后面太長(cháng)了就不放了。。。。
完成了基礎的多線(xiàn)程框架搭建后,我們來(lái)為每個(gè)線(xiàn)程執行過(guò)程中加入賣(mài)票的程序
首先要解決的一個(gè)問(wèn)題是:票存在哪里?。毋庸置疑的是由于是多線(xiàn)程并發(fā)的售票,因此票這個(gè)變量一定是被多個(gè)線(xiàn)程所共享的,而不能是每個(gè)線(xiàn)程對象自己的屬性。
一個(gè)可行的方案是在TicketSellet
類(lèi)中定義靜態(tài)的票計數,這樣所有的線(xiàn)程訪(fǎng)問(wèn)票的時(shí)候訪(fǎng)問(wèn)的都是同一個(gè)票計數變量。
另一個(gè)可行方案是使用一個(gè)對象管理票,票計數是這個(gè)對象的成員,并且讓每個(gè)TicketSeller
持有相同的對象。那么多個(gè)線(xiàn)程也同樣共享票計數。
當然,可行的方案還有很多,現在我們先來(lái)實(shí)現第一種,在之后的改進(jìn)中,我們還會(huì )用到第二種。
先來(lái)一個(gè)沒(méi)有加鎖的寫(xiě)法,看看他的問(wèn)題
//售票類(lèi),實(shí)現Runnable接口,可以作為線(xiàn)程執行對象 class TicketSeller implements Runnable{ //票數 private static int tickets = 100; //該售票窗口編號 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ì )等待其他線(xiàn)程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
這段不加鎖的代碼會(huì )遇到許多很尷尬的問(wèn)題,首先一個(gè),多線(xiàn)程之間的重復賣(mài)票:
除了重復賣(mài)票,還有超額賣(mài)票的行為:
這當然是不能容忍的,解決辦法是在賣(mài)票過(guò)程對tickets
變量加鎖,使得每次只能有一個(gè)線(xiàn)程進(jìn)入賣(mài)票的環(huán)節而其他線(xiàn)程只能循環(huán)等待:
但是這樣處理并不能完全結局上面的問(wèn)題,盡管每次只能一個(gè)線(xiàn)程進(jìn)入賣(mài)票階段阻止了重復賣(mài)票。但是超額賣(mài)票的行為依舊會(huì )發(fā)生:
好嘛,這次非常嚴重
原因嗎其實(shí)并不復雜,我們加鎖只是能阻止多個(gè)進(jìn)程進(jìn)入賣(mài)票程序,但是會(huì )有其他程序達成判斷條件,執行到賣(mài)票程序之前等待進(jìn)入,如果一個(gè)線(xiàn)程將票賣(mài)完而此時(shí)有其他程序剛好等待進(jìn)入,那么就會(huì )出現上面的情況。
所以我們還需要加上一道保險:
經(jīng)過(guò)這樣的處理,票子就可以放心的賣(mài)出而不用擔心重或者賣(mài)超了
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創(chuàng )建5個(gè)線(xiàn)程并啟動(dòng)他們 //注意一定要使用Thread類(lèi)創(chuàng )建線(xiàn)程并使用start方法啟動(dòng) //而不是直接創(chuàng )建TicketSeller對象調用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類(lèi),實(shí)現Runnable接口,可以作為線(xiàn)程執行對象 class TicketSeller implements Runnable{ //票數 private static int tickets = 100; //同步鎖 private static Object lock = new Object(); //該售票窗口編號 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ì )等待其他線(xiàn)程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
在前面我們還提出了另一種方案,就是使用一個(gè)對象管理票的售賣(mài)。這種方案就不展開(kāi)啰嗦了,直接上代碼:
public class Ticket { public static void main(String[] args) { //創(chuàng )建一個(gè)票管理對象,票數為100 TicketSet ts = new TicketSet(100); //創(chuàng )建5個(gè)線(xiàn)程,使用同一個(gè)票管理對象 for(int i = 1;i <= 5;i++) { new Thread(new TicketSeller(ts, i)).start(); } } } //票管理類(lèi) class TicketSet{ //票數 private int tickets; public TicketSet(int tickets) { this.tickets = tickets; } private boolean hasTicket() { return tickets > 0; } //售票方法,使用同步鎖,每次只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)該方法 //返回結果為是否賣(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{ //票管理對象 private TicketSet ts; private int code; public TicketSeller(TicketSet ts,int code) { this.ts = ts; this.code = code; } @Override public void run() { //嘗試調用票管理的售票方法,售票成功后休眠一小會(huì ) while(ts.sellTicket(code)){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
到此這篇關(guān)于Java實(shí)戰之多線(xiàn)程模擬站點(diǎn)售票的文章就介紹到這了,更多相關(guān)多線(xiàn)程模擬站點(diǎn)售票內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站