前言
經常看到有sleep和wait的區別,大都千篇一律,都會談及到wait會釋放鎖,并且本線程將會進入wait狀態,只有回調了notify或者notifyAll方法之后才能夠喚醒,才能夠繼續執行。那么這一個過程究竟說的是什么?這么抽象的描述,能否有一個例子呢?本文主要講的就是這個問題。
問題實例化
這里還是用最為常見的銀行賬戶作為實例。假設有兩個賬戶,A賬戶資金100,B賬戶資金200,那么如果需要A轉給B120元,那么肯定是不行的,但是資金轉賬,欠債總是要還的,所以不能就此罷休吧(如果你有錢例外),所以這個操作只能暫時掛起。接下來,B可能欠了A一些人情(暫且這么認為),所以需要給A轉50塊錢,由于B有200元,足夠了,所以直接就轉了。這個時候A資金有了150,足夠轉給B120元了,所以前面掛起的操作需要繼續執行。(上述只是一個例子,不要轉牛角尖,說什么B轉A 50,還不如直接讓A轉給B 70)
語言描述
上面已經描述得很清楚了,這里主要是說明幾個關鍵點:
1)掛起,我們這里是將當前線程通過wait方法操作的
2)將前面掛起的操作恢復,需要在當前線程(非1)線程)notify或notifyAll
源碼分析
package hudson;
public class MainWork {
public static void main(String[] args) {
account = new int[]{100,200};
MainWork mainWork = new MainWork();
//需要注意的是子線程必須在主線程之前回調
//原因在于,我們的主線程在回調transfer方法時由于
//轉錢者資金不足會導致主線程進入wait狀態,需要等待
//其他線程再次進入該對象拿到對象鎖,并通過回調notify或者notifyAll
//方法來喚醒主線程繼續執行
new Thread(){
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainWork.transfer(1, 0, 10);
};
}.start();
//由于賬戶0資金不足120,本方法回調會導致主線程進入wait狀態
mainWork.transfer(0, 1, 120);
}
private static int[] account;
public synchronized void transfer(int from,int to,int count){
while(account[from]<count){
try {
System.out.println("對不起,資金不足,進入等待狀態");
//進入等待,會釋放鎖。需要外界再次回調本方法(拿到本對象鎖),并
//回調notify喚醒線程,以便查看是否資金是否充足
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("從"+from+"賬戶轉錢到"+to+"賬戶"+count+"元錢");
account[from] -= count;
account[to] += count;
notifyAll();
}
}
上述代碼非常簡單,但是需要有幾點注意:
1)子線程必須在主線程之前回調,因為主線程完成的是A轉B的第一次操作,由于A太窮,沒法支付,所以會導致進入wait狀態,即進入阻塞狀態。如果你把子線程放在后面,那么子線程是沒有機會執行的
2)在wait外部是用一個while循環包裹的,為什么不能用if呢?
原因是,如果使用if包裹,假設B轉給A的資金不足以使得A的資金超過120元,即不足以使得A后面有能夠還給B 120元,例如B轉給A 10元,那么A資金是110元,這個時候是不滿足A轉給B 120元的條件的,所以不應該往后面執行。啰嗦了這么一大堆,簡單地說就是在被其他線程喚醒之后,我們還需要判斷條件是否符合繼續執行