Java實戰之多線程模擬站點售票
一、實驗題目
二、分析
哦吼,這次的實驗題目是一道非常經典的多線程買票問題。題目要求我們創建5個線程來模擬賣票,當然這其中就包含多線程存在也就是我們要解決的問題,重復賣票和超額賣票。即多個窗口賣出同一張票以及窗口賣出非正數編號的票。
不過這個問題可以先放一下,我們先來創建基礎的線程模型,並在主方法中創建五個線程讓他們跑起來;
話不多說,上代碼。
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創建5個線程並啟動他們 //註意一定要使用Thread類創建線程並使用start方法啟動 //而不是直接創建TicketSeller對象調用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類,實現Runnable接口,可以作為線程執行對象 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 + "號窗口"); //為瞭使線程能夠交替執行,打印完成語句讓線程休眠一小會 try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }
代碼的含義和需要註意的點都在註釋裡面瞭,一定要看註釋!!!
運行結果就是:
後面太長瞭就不放瞭。。。。
完成瞭基礎的多線程框架搭建後,我們來為每個線程執行過程中加入賣票的程序
首先要解決的一個問題是:票存在哪裡?。毋庸置疑的是由於是多線程並發的售票,因此票這個變量一定是被多個線程所共享的,而不能是每個線程對象自己的屬性。
一個可行的方案是在TicketSellet
類中定義靜態的票計數,這樣所有的線程訪問票的時候訪問的都是同一個票計數變量。
另一個可行方案是使用一個對象管理票,票計數是這個對象的成員,並且讓每個TicketSeller
持有相同的對象。那麼多個線程也同樣共享票計數。
當然,可行的方案還有很多,現在我們先來實現第一種,在之後的改進中,我們還會用到第二種。
先來一個沒有加鎖的寫法,看看他的問題
//售票類,實現Runnable接口,可以作為線程執行對象 class TicketSeller implements Runnable{ //票數 private static int tickets = 100; //該售票窗口編號 private int code; public TicketSeller(int code) { this.code = code; } @Override public void run() { //如果有票就一直賣 while(tickets > 0) { System.out.println(code + "_____" + tickets--); //賣過票之後休眠一小會等待其他線程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
這段不加鎖的代碼會遇到許多很尷尬的問題,首先一個,多線程之間的重復賣票:
除瞭重復賣票,還有超額賣票的行為:
這當然是不能容忍的,解決辦法是在賣票過程對tickets
變量加鎖,使得每次隻能有一個線程進入賣票的環節而其他線程隻能循環等待:
但是這樣處理並不能完全結局上面的問題,盡管每次隻能一個線程進入賣票階段阻止瞭重復賣票。但是超額賣票的行為依舊會發生:
好嘛,這次非常嚴重
原因嗎其實並不復雜,我們加鎖隻是能阻止多個進程進入賣票程序,但是會有其他程序達成判斷條件,執行到賣票程序之前等待進入,如果一個線程將票賣完而此時有其他程序剛好等待進入,那麼就會出現上面的情況。
所以我們還需要加上一道保險:
經過這樣的處理,票子就可以放心的賣出而不用擔心重或者賣超瞭
三、完整代碼:
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創建5個線程並啟動他們 //註意一定要使用Thread類創建線程並使用start方法啟動 //而不是直接創建TicketSeller對象調用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類,實現Runnable接口,可以作為線程執行對象 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() { //如果有票就一直賣 while(tickets > 0) { synchronized (lock) { //如果票賣完瞭則跳出 if(tickets <= 0) { break; } System.out.println(code + "_____" + tickets--); //賣過票之後休眠一小會等待其他線程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
在前面我們還提出瞭另一種方案,就是使用一個對象管理票的售賣。這種方案就不展開囉嗦瞭,直接上代碼:
public class Ticket { public static void main(String[] args) { //創建一個票管理對象,票數為100 TicketSet ts = new TicketSet(100); //創建5個線程,使用同一個票管理對象 for(int i = 1;i <= 5;i++) { new Thread(new TicketSeller(ts, i)).start(); } } } //票管理類 class TicketSet{ //票數 private int tickets; public TicketSet(int tickets) { this.tickets = tickets; } private boolean hasTicket() { return tickets > 0; } //售票方法,使用同步鎖,每次隻能有一個線程訪問該方法 //返回結果為是否賣出去票 synchronized public boolean sellTicket(int code) { if(hasTicket()) { System.out.println(code + "_____" + tickets--); return true; }else { return false; } } } //售票類 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() { //嘗試調用票管理的售票方法,售票成功後休眠一小會 while(ts.sellTicket(code)){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
到此這篇關於Java實戰之多線程模擬站點售票的文章就介紹到這瞭,更多相關多線程模擬站點售票內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!