1. 程式人生 > >Java多執行緒-45-多執行緒安全問題--火車票賣票

Java多執行緒-45-多執行緒安全問題--火車票賣票

這篇通過火車票賣票來逐步演示多執行緒的安全問題,加入一共賣100張火車票,分成4個視窗賣。也就是,四個視窗就是4個執行緒,如何保證4個視窗賣票都正確。

1.先實現4個視窗賣票的程式碼

package thread;

public class Demo3_Ticket {

	public static void main(String[] args) {
		//建立四個執行緒
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();

	}

}

class Ticket extends Thread {
	
	private int tickets = 100;
	public void run () {
		while(true){
			if(tickets == 0){
				break;
			}
			System.out.println(getName() + "...這是第" + tickets-- + "號票");
		}
	}
	
}

上面的條件是,只要第幾號票不等於0,就做tickets--操作。執行一次,發現實際上四個執行緒賣了400張票。因為new了四個ticket物件,每一個ticket物件都有自己的tickets=100這個成員變數。所以,變成了各自賣自己100張火車票的情況。

2.解決各自賣各自火車票的問題

上面每個物件都有自己100這個成員變數,我們需要把這個變數設定成共享,這樣四個執行緒就能共享100張,所以在tickets設定static,看看效果。

private static int tickets = 100;

執行結果是沒有問題。但是我們在if語句和列印語句之間可能還需要執行其他程式碼,如果新增執行其他程式碼,賣票的結果就有問題。程式碼修改如下。

package thread;

public class Demo3_Ticket {

	public static void main(String[] args) {
		//建立四個執行緒
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();

	}

}

class Ticket extends Thread {
	
	private static int tickets = 100;
	public void run () {
		while(true){
			if(tickets == 0){
				break;
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(getName() + "...這是第" + tickets-- + "號票");
		}
	}
	
}

執行一下,神奇的發現,會一直執行下去,票可以賣到負數。如果不手動停止,會一直賣負數。有人說我們把if條件 tickets == 0改成<=0不就不會賣負數票了嘛。真的嗎。

Thread-2...這是第2號票
Thread-3...這是第1號票
Thread-1...這是第0號票
Thread-0...這是第-1號票
Thread-2...這是第-2號票

為什麼還是有負數呢?上面我們添加了sleep程式碼,有可能有一個執行緒或者4個執行緒都進行了sleep, 例如執行緒1開始進行sleep,接著執行緒2,執行緒3,執行緒4都依次進入sleep,某一個時候,執行緒1起來幹活了,發現ticket=0,不符合<=0的條件就會跳出迴圈。執行列印語句,tickets--的結果就為-1,執行緒2跟著後面,也跳出迴圈,由於前面等於-1,所以執行緒2列印ticket--的結果就是-2. 如何解決這個問題,原因就是tickets這個變數,在迴圈判斷和tickets--都用到,但是這段程式碼沒有進行同步。

3.解決同步tickets問題

在titck類的方法中新增synchronized同步。

package thread;

public class Demo3_Ticket {

	public static void main(String[] args) {
		//建立四個執行緒
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();

	}

}

class Ticket extends Thread {
	
	private static int tickets = 100;
	public void run () {
		while(true){
			synchronized(this) {
				if(tickets <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(getName() + "...這是第" + tickets-- + "號票");
			}
		}
	}
	
}

執行之後,還是有負數票。原因是這裡不能新增this為鎖物件,因為new了四個物件,this代表不同new ticket物件,這個時候需要把this 改成Ticket.class就行。出了位元組碼物件,如果要新增一個成員物件作為鎖物件,那麼這個成員物件一定要用static修飾。

package thread;

public class Demo3_Ticket {

	public static void main(String[] args) {
		//建立四個執行緒
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();

	}

}

class Ticket extends Thread {
	
	private static int tickets = 100;
	private static Object obj = new Object();
	public void run () {
		while(true){
			synchronized(obj) {
				if(tickets <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(getName() + "...這是第" + tickets-- + "號票");
			}
		}
	}
	
}

建議還是寫對位元組碼物件做鎖物件就好。