如果線程在等待時(shí)接到通知,但線程等待的條件還不滿足,此時(shí),線程接到的就是早期通知,如果條件滿足的時(shí)間很短,但很快又改變了,而變得不再滿足,這時(shí)也將發(fā)生早期通知。這種現(xiàn)象聽起來很奇怪,下面通過一個(gè)示例程序來說明問題。
很簡(jiǎn)單,兩個(gè)線程等待刪除 List 中的元素,同時(shí)另外一個(gè)線程正要向其中添加項(xiàng)目。代碼如下:
import java.util.*;
public class EarlyNotify extends Object {
private List list;
public EarlyNotify() {
list = Collections.synchronizedList(new LinkedList());
}
public String removeItem() throws InterruptedException {
print("in removeItem() - entering");
synchronized ( list ) {
if ( list.isEmpty() ) { //這里用if語句會(huì)發(fā)生危險(xiǎn)
print("in removeItem() - about to wait()");
list.wait();
print("in removeItem() - done with wait()");
}
//刪除元素
String item = (String) list.remove(0);
print("in removeItem() - leaving");
return item;
}
}
public void addItem(String item) {
print("in addItem() - entering");
synchronized ( list ) {
//添加元素
list.add(item);
print("in addItem() - just added: '" + item + "'");
//添加后,通知所有線程
list.notifyAll();
print("in addItem() - just notified");
}
print("in addItem() - leaving");
}
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final EarlyNotify en = new EarlyNotify();
Runnable runA = new Runnable() {
public void run() {
try {
String item = en.removeItem();
print("in run() - returned: '" +
item + "'");
} catch ( InterruptedException ix ) {
print("interrupted!");
} catch ( Exception x ) {
print("threw an Exception!!!\n" + x);
}
}
};
Runnable runB = new Runnable() {
public void run() {
en.addItem("Hello!");
}
};
try {
//啟動(dòng)第一個(gè)刪除元素的線程
Thread threadA1 = new Thread(runA, "threadA1");
threadA1.start();
Thread.sleep(500);
//啟動(dòng)第二個(gè)刪除元素的線程
Thread threadA2 = new Thread(runA, "threadA2");
threadA2.start();
Thread.sleep(500);
//啟動(dòng)增加元素的線程
Thread threadB = new Thread(runB, "threadB");
threadB.start();
Thread.sleep(10000); // wait 10 seconds
threadA1.interrupt();
threadA2.interrupt();
} catch ( InterruptedException x ) {}
}
}
執(zhí)行結(jié)果如下:
http://wiki.jikexueyuan.com/project/java-concurrency/images/notifyall.jpg" alt="" />
分析:首先啟動(dòng) threadA1,threadA1 在 removeItem()中調(diào)用 wait(),從而釋放 list 上的對(duì)象鎖。再過 500ms,啟動(dòng) threadA2,threadA2 調(diào)用 removeItem(),獲取 list 上的對(duì)象鎖,也發(fā)現(xiàn)列表為空,從而在 wait()方法處阻塞,釋放 list 上的對(duì)象鎖。再過 500ms 后,啟動(dòng) threadB,并調(diào)用 addItem,獲得 list 上的對(duì)象鎖,并在 list 中添加一個(gè)元素,同時(shí)用 notifyAll 通知所有線程。
threadA1 和 threadA2 都從 wait()返回,等待獲取 list 對(duì)象上的對(duì)象鎖,并試圖從列表中刪除添加的元素,這就會(huì)產(chǎn)生麻煩,只有其中一個(gè)操作能成功。假設(shè) threadA1 獲取了 list 上的對(duì)象鎖,并刪除元素成功,在退出 synchronized 代碼塊時(shí),它便會(huì)釋放 list 上的對(duì)象鎖,此時(shí) threadA2 便會(huì)獲取 list 上的對(duì)象鎖,會(huì)繼續(xù)刪除 list 中的元素,但是 list 已經(jīng)為空了,這便會(huì)拋出 IndexOutOfBoundsException。
要避免以上問題只需將 wait 外圍的 if 語句改為 while 循環(huán)即可,這樣當(dāng) list 為空時(shí),線程便會(huì)繼續(xù)等待,而不會(huì)繼續(xù)去執(zhí)行刪除 list 中元素的代碼。
修改后的執(zhí)行結(jié)果如下:
http://wiki.jikexueyuan.com/project/java-concurrency/images/niotifyall1.jpg" alt="" />
總結(jié):在使用線程的等待/通知機(jī)制時(shí),一般都要在 while 循環(huán)中調(diào)用 wait()方法,滿足條件時(shí),才讓 while循環(huán)退出,這樣一般也要配合使用一個(gè) boolean 變量(或其他能判斷真假的條件,如本文中的 list.isEmpty()),滿足 while 循環(huán)的條件時(shí),進(jìn)入 while 循環(huán),執(zhí)行 wait()方法,不滿足 while 循環(huán)的條件時(shí),跳出循環(huán),執(zhí)行后面的代碼。