JAVA線程虛假喚醒


線程虛假喚醒問題描述

​ 在JDK API文檔中,關於Object類的wait()方法有這樣一句話描述"線程也可以喚醒,而不會被通知,中斷或超時,即所謂的虛假喚醒 。 雖然這在實踐中很少會發生,但應用程序必須通過測試應該使線程被喚醒的條件來防范,並且如果條件不滿足則繼續等待",如下圖所示:

​ 在多線程的情況下,當多個線程執行了wait()方法后,需要其它線程執行notify()或者notifyAll()方法去喚醒,假如被阻塞的多個線程都被喚醒,但實際情況是被喚醒的線程中有一部分線程是不應該被喚醒的,那么對於這些不應該被喚醒的線程而言就是虛假喚醒。

問題復現

生產者與消費者問題

​ 假設當前有4個線程分別為A,B,C,D,其中A,B線程是生產者,C,D線程是消費者,當A和B線程生產了一個數據后就去通知消費者去消費,C和D消費掉這一個數據后就通知生產者去生產,數據的大小為1。也就是說正常情況下,數據只會有0和1兩種狀態,0表示生產者該生產數據了,1表示消費者該消費數據了。

package producer_consumer;

public class PVTest {
    public static void main(String[] args) {
        Data data = new Data();
        //生產者線程A
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        //生產者線程B
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        //消費者線程C
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        //消費者線程D
        new Thread(() -> {
            for (int i = 0;i < 5;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

//數據類
class Data {
    //表示數據個數
    private int number = 0;
    public synchronized void increment() throws InterruptedException {
        //關鍵點,這里應該使用while循環
        if (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"生產了數據:"+number);
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        //關鍵點,這里應該使用while循環
        if (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"消費了數據:"+number);
        this.notifyAll();
    }
}

程序結果

結果分析

​ 可以看到上述結果出現了data為2的情況,不符合之前的預期,出現問題的場景是這樣的:當data為1的時候,線程A和B先后獲取鎖去生產數據的時候會被阻塞住,然后消費者C或者D消費掉數據后去notifyAll()喚醒了線程A和B,被喚醒的A和B沒有再次去判斷data狀態,就去執行后續增加數據的邏輯了,導致兩個生產者都執行了increment(),最終data出現了2這種情況。也就是說線程A和B有一個是不應該被喚醒的卻被喚醒了,出現這個問題的關鍵點在於程序中使用到了if判斷,只判斷了一次data的狀態,應該使用while循環去判斷

虛假喚醒問題解決

​ 正如JDK API文檔中所說在寫程序時候應該用while去替代if,上述生產者和消費者代碼中,將Data類中的increment()和decrement()方法中的if換為while即可避免線程虛假喚醒的問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM