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!

推薦閱讀: