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-- + "號票");
}
}
}
}
建議還是寫對位元組碼物件做鎖物件就好。