鍍金池/ 教程/ Java/ notifyAll 造成的早期通知問題
并發(fā)新特性—信號(hào)量 Semaphore
線程間協(xié)作:wait、notify、notifyAll
notify 通知的遺漏
notifyAll 造成的早期通知問題
多線程的實(shí)現(xiàn)方法
深入 Java 內(nèi)存模型(1)
多線程環(huán)境下安全使用集合 API
并發(fā)新特性—Lock 鎖與條件變量
生產(chǎn)者—消費(fèi)者模型
深入 Java 內(nèi)存模型(2)
線程中斷
Volatile 關(guān)鍵字(上)
并發(fā)新特性—阻塞隊(duì)列與阻塞棧
可重入內(nèi)置鎖
守護(hù)線程與線程阻塞
并發(fā)新特性—障礙器 CyclicBarrier
Volatile 關(guān)鍵字(下)
synchronized 關(guān)鍵字
synchronized 的另個(gè)一重要作用:內(nèi)存可見性
并發(fā)新特性—Executor 框架與線程池
并發(fā)性與多線程介紹
死鎖
實(shí)現(xiàn)內(nèi)存可見性的兩種方法比較:synchronized 和 Volatile
線程掛起、恢復(fù)與終止

notifyAll 造成的早期通知問題

如果線程在等待時(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í)行后面的代碼。