Java實(shí)戰(zhàn)之多線程模擬站點(diǎn)售票
一、實(shí)驗(yàn)題目
二、分析
哦吼,這次的實(shí)驗(yàn)題目是一道非常經(jīng)典的多線程買票問題。題目要求我們創(chuàng)建5個(gè)線程來(lái)模擬賣票,當(dāng)然這其中就包含多線程存在也就是我們要解決的問題,重復(fù)賣票和超額賣票。即多個(gè)窗口賣出同一張票以及窗口賣出非正數(shù)編號(hào)的票。
不過(guò)這個(gè)問題可以先放一下,我們先來(lái)創(chuàng)建基礎(chǔ)的線程模型,并在主方法中創(chuàng)建五個(gè)線程讓他們跑起來(lái);
話不多說(shuō),上代碼。
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創(chuàng)建5個(gè)線程并啟動(dòng)他們 //注意一定要使用Thread類創(chuàng)建線程并使用start方法啟動(dòng) //而不是直接創(chuàng)建TicketSeller對(duì)象調(diào)用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類,實(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)都在注釋里面了,一定要看注釋?。?!
運(yùn)行結(jié)果就是:
后面太長(zhǎng)了就不放了。。。。
完成了基礎(chǔ)的多線程框架搭建后,我們來(lái)為每個(gè)線程執(zhí)行過(guò)程中加入賣票的程序
首先要解決的一個(gè)問題是:票存在哪里?。毋庸置疑的是由于是多線程并發(fā)的售票,因此票這個(gè)變量一定是被多個(gè)線程所共享的,而不能是每個(gè)線程對(duì)象自己的屬性。
一個(gè)可行的方案是在TicketSellet
類中定義靜態(tài)的票計(jì)數(shù),這樣所有的線程訪問票的時(shí)候訪問的都是同一個(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è)沒有加鎖的寫法,看看他的問題
//售票類,實(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() { //如果有票就一直賣 while(tickets > 0) { System.out.println(code + "_____" + tickets--); //賣過(guò)票之后休眠一小會(huì)等待其他線程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
這段不加鎖的代碼會(huì)遇到許多很尷尬的問題,首先一個(gè),多線程之間的重復(fù)賣票:
除了重復(fù)賣票,還有超額賣票的行為:
這當(dāng)然是不能容忍的,解決辦法是在賣票過(guò)程對(duì)tickets
變量加鎖,使得每次只能有一個(gè)線程進(jìn)入賣票的環(huán)節(jié)而其他線程只能循環(huán)等待:
但是這樣處理并不能完全結(jié)局上面的問題,盡管每次只能一個(gè)線程進(jìn)入賣票階段阻止了重復(fù)賣票。但是超額賣票的行為依舊會(huì)發(fā)生:
好嘛,這次非常嚴(yán)重
原因嗎其實(shí)并不復(fù)雜,我們加鎖只是能阻止多個(gè)進(jìn)程進(jìn)入賣票程序,但是會(huì)有其他程序達(dá)成判斷條件,執(zhí)行到賣票程序之前等待進(jìn)入,如果一個(gè)線程將票賣完而此時(shí)有其他程序剛好等待進(jìn)入,那么就會(huì)出現(xiàn)上面的情況。
所以我們還需要加上一道保險(xiǎn):
經(jīng)過(guò)這樣的處理,票子就可以放心的賣出而不用擔(dān)心重或者賣超了
三、完整代碼:
public class Ticket { public static void main(String[] args) { for(int i = 1;i <= 5;i++) { //創(chuàng)建5個(gè)線程并啟動(dòng)他們 //注意一定要使用Thread類創(chuàng)建線程并使用start方法啟動(dòng) //而不是直接創(chuàng)建TicketSeller對(duì)象調(diào)用run方法!!!!!! new Thread(new TicketSeller(i)).start(); } } } //售票類,實(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() { //如果有票就一直賣 while(tickets > 0) { synchronized (lock) { //如果票賣完了則跳出 if(tickets <= 0) { break; } System.out.println(code + "_____" + tickets--); //賣過(guò)票之后休眠一小會(huì)等待其他線程操作 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
在前面我們還提出了另一種方案,就是使用一個(gè)對(duì)象管理票的售賣。這種方案就不展開啰嗦了,直接上代碼:
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(); } } } //票管理類 class TicketSet{ //票數(shù) private int tickets; public TicketSet(int tickets) { this.tickets = tickets; } private boolean hasTicket() { return tickets > 0; } //售票方法,使用同步鎖,每次只能有一個(gè)線程訪問該方法 //返回結(jié)果為是否賣出去票 synchronized public boolean sellTicket(int code) { if(hasTicket()) { System.out.println(code + "_____" + tickets--); return true; }else { return false; } } } //售票類 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)文章
idea為java程序添加啟動(dòng)參數(shù)的問題解析(program?arguments,vm?arguments,Envi
這篇文章主要介紹了idea為java程序添加啟動(dòng)參數(shù)的問題解析(program?arguments,vm?arguments,Environment?variable)并在程序中獲取使用,本文給大家分享問題描述及解決方法,需要的朋友可以參考下2023-09-09Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解
這篇文章主要介紹了Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11Java中jar包運(yùn)行后顯示:沒有主清單屬性的解決方案
這篇文章主要介紹了Java中jar包運(yùn)行后顯示:沒有主清單屬性的解決方案,文中給大家分析了三個(gè)主要原因,并通過(guò)代碼示例和圖文講解的非常詳細(xì),需要的朋友可以參考下2024-04-04Mybatis配置之<typeAliases>別名配置元素解析
這篇文章主要介紹了Mybatis配置之<typeAliases>別名配置元素解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07MyBatis攔截器:給參數(shù)對(duì)象屬性賦值的實(shí)例
下面小編就為大家?guī)?lái)一篇MyBatis攔截器:給參數(shù)對(duì)象屬性賦值的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Java中BigDecimal與0比較的一個(gè)坑實(shí)戰(zhàn)記錄
BigDecimal屬于大數(shù)據(jù),精度極高,不屬于基本數(shù)據(jù)類型,屬于java對(duì)象,下面這篇文章主要給大家介紹了關(guān)于Java中BigDecimal與0比較的一個(gè)坑的相關(guān)資料,需要的朋友可以參考下2022-12-12一文教你如何使用AES對(duì)接口參數(shù)進(jìn)行加密
這篇文章主要是想為大家介紹一下如何使用AES實(shí)現(xiàn)對(duì)接口參數(shù)進(jìn)行加密,文中的示例代碼簡(jiǎn)潔易懂,具有一定的借鑒價(jià)值,需要的小伙伴可以了解一下2023-08-08PowerDesigner連接數(shù)據(jù)庫(kù)的實(shí)例詳解
這篇文章主要介紹了PowerDesigner連接數(shù)據(jù)庫(kù)的實(shí)例詳解的相關(guān)資料,如有疑問請(qǐng)留言或者到本站社區(qū)交流討論,需要的朋友可以參考下2017-10-10