1. 程式人生 > >Java多執行緒模擬售票系統

Java多執行緒模擬售票系統

Java建立多執行緒的兩種基本方法:

方法1.繼承Thread類
(1)定義子類,繼承Thread類,重寫該類的run()方法作為執行緒執行體;
(2)建立該子類的例項作為執行緒物件;

(3)呼叫執行緒物件的start()方法來啟動執行緒;

我們以模擬火車售票系統為例:

public class SellTicket {

	public static void main(String[] args) {
		for(int i=1; i<4; i++){
			TicketWindow tw = new TicketWindow();
			tw.setName("TicketWindow-" + i);
			tw.start();
		}
	}
}

class TicketWindow extends Thread{
	private int tickets = 100;//車票總量
	@Override
	public void run(){
		while(true){
			if(tickets>0){
				System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
                tickets--;                                                                                              
                System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
                try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			else{
				System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
				break;
			}
		}
	}
}

方法2.實現Runnable介面

(1)定義類實現Runnable介面,重寫run()方法;
(2)建立該實現類的例項,以該例項作為Thread的target來建立Thread物件;

(3)呼叫該Thread物件的start()方法來啟動該執行緒;

還是以模擬火車售票視窗為例:

public class SellTicket {

	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		for(int i=1; i<4; i++){
			Thread t = new Thread(tw,"TickWindow-" + i);
			t.start();
		}
	}
}

class TicketWindow implements Runnable{
	private int tickets = 100;//車票總量
	@Override
	public void run(){
		while(true){
			if(tickets>0){
				System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
                tickets--;                                                                                              
                System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
                try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			else{
				System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
				break;
			}
		}
	}
}

注意:

繼承Thread類與實現Runnable介面的區別及優缺點對比:

繼承Thread類實現Runnable介面
能否繼承其他類不能
如何訪問當前執行緒this即為當前執行緒Thread.currentThread()
是否共享同一target是,適合多個相同執行緒處理同一份資源的情況
因此,一般推薦採用實現Runnable介面/Callable介面的方式建立多執行緒;

雖然上面實現Runnable介面的火車售票系統共享了車票總數,但是沒有控制同一時刻只能有一個執行緒進行賣票操作,因此需要同步關鍵字 synchronized 進行控制,

執行緒同步分為同步塊和同步方法:

1.同步塊

同步塊的語法格式為:

synchronized(object){
    //...同步程式碼塊
}

上述程式碼的意思是,要想執行同步程式碼塊,必須先獲得同步監視器的鎖定.

其中object為同步監視器,一般使用可能被併發訪問的共享資源當同步監視器;

下面給出java多執行緒模擬火車售票系統的同步塊實現:

	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		for(int i=1; i<4; i++){
			Thread t = new Thread(tw,"TickWindow-" + i);
			t.start();
		}
	}
}

class TicketWindow implements Runnable{
	private int tickets = 10;//車票總量
	@Override
	public void run(){
		while(true){
			synchronized (this) {
				if(tickets>0){
					System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
	                tickets--;                                                                                              
	                System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
	                try {
	                	//休眠100ms賣票完會報錯ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2JDWP exit error AGENT_ERROR_NO_JNI_ENV(183):  [../../../src/share/back/util.c:820]
						//Thread.sleep(100);
						Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				else{
					System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
					break;
				}
			}
		}
	}
}

輸出:

TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-3準備出票,剩餘票數:2張
TickWindow-3賣出一張,剩餘票數:1張
TickWindow-3準備出票,剩餘票數:1張
TickWindow-3賣出一張,剩餘票數:0張
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!

2.同步方法.

同步方法就是使用 synchronized 關鍵字修飾某個方法,synchronized 修飾的例項方法(非static方法)的同步監視器是this.

使用同步方法解決共享資源的多執行緒訪問衝突的一般方式是:

        //把修改共享資源的方法使用synchronized進行修飾
	public synchronized void someMethod(){
		//do something...
	}
	//在run()方法中呼叫該同步方法
	public void run(){
		someMethod();
	}

下面給出使用同步方法實現的模擬火車售票系統:

public class SellTicket {

	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		for(int i=1; i<4; i++){
			Thread t = new Thread(tw,"TickWindow-" + i);
			t.start();
		}
	}
}

class TicketWindow implements Runnable{
	private int tickets = 10;//車票總量
	@Override
	public void run(){
		while(true){
			sellTicket();
		}
	}
	public synchronized void sellTicket(){
			if(tickets>0){
				System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
	            tickets--;                                                                                              
	            System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
	            try {
					Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			else{
				System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
	}
}

輸出:

TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!

注意:while(true){}要放在run()方法裡面,而不是sell方法裡. 否則會出現某一個視窗一直把票賣完的情況.

要想讓售票視窗在售完票之後停止,需要在run()方法裡的作條件限制,修改如下:

public class SellTicket {

	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		for(int i=1; i<4; i++){
			Thread t = new Thread(tw,"TickWindow-" + i);
			t.start();
		}
	}
}

class TicketWindow implements Runnable{
	private int tickets = 10;//車票總量
	@Override
	public void run(){
		while(true){
			if(tickets>0){
				sellTicket();
			}
			else{
				System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
				break;
			}
		}
	}
	public synchronized void sellTicket(){
		if(tickets>0){
			System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
            tickets--;                                                                                              
            System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
            try {
				Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

輸出如下:

TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-3餘票不足,停止售票!
TickWindow-2餘票不足,停止售票!

注意!

使用 synchronized 修飾run()方法是無效的:

public class SellTicket {

	public static void main(String[] args) {
		TicketWindow tw = new TicketWindow();
		for(int i=1; i<4; i++){
			Thread t = new Thread(tw,"TickWindow-" + i);
			t.start();
		}
	}
}

class TicketWindow implements Runnable{
	private int tickets = 10;//車票總量
	
	@Override
	public synchronized void run(){
		while(true){
			if(tickets>0){
				System.out.println(Thread.currentThread().getName() + "準備出票,剩餘票數:" + tickets + "張");                    
	            tickets--;                                                                                              
	            System.out.println(Thread.currentThread().getName() + "賣出一張,剩餘票數:" + tickets + "張");
	            try {
					Thread.sleep(500);//出票成功後讓當前售票視窗睡眠,以便讓其他售票視窗賣票
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			else{
				System.out.println(Thread.currentThread().getName() + "餘票不足,停止售票!");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

會輸出:

TickWindow-1準備出票,剩餘票數:10張
TickWindow-1賣出一張,剩餘票數:9張
TickWindow-1準備出票,剩餘票數:9張
TickWindow-1賣出一張,剩餘票數:8張
TickWindow-1準備出票,剩餘票數:8張
TickWindow-1賣出一張,剩餘票數:7張
TickWindow-1準備出票,剩餘票數:7張
TickWindow-1賣出一張,剩餘票數:6張
TickWindow-1準備出票,剩餘票數:6張
TickWindow-1賣出一張,剩餘票數:5張
TickWindow-1準備出票,剩餘票數:5張
TickWindow-1賣出一張,剩餘票數:4張
TickWindow-1準備出票,剩餘票數:4張
TickWindow-1賣出一張,剩餘票數:3張
TickWindow-1準備出票,剩餘票數:3張
TickWindow-1賣出一張,剩餘票數:2張
TickWindow-1準備出票,剩餘票數:2張
TickWindow-1賣出一張,剩餘票數:1張
TickWindow-1準備出票,剩餘票數:1張
TickWindow-1賣出一張,剩餘票數:0張
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!
TickWindow-1餘票不足,停止售票!

原因見:

參考: