1. 程式人生 > 程式設計 >Java wait和notify虛假喚醒原理

Java wait和notify虛假喚醒原理

自己在此記錄一下,方便日後複習。

虛假喚醒的概念

jdk官方文件解釋:

Java wait和notify虛假喚醒原理

所以說在wait和notify一塊使用時,如果使用if作為條件時,會有虛假喚醒的情況發生,所以必須使用while作為迴圈條件。下面來舉例實驗:

首先,建立一個資源類:(在多執行緒中,一般都是資源類和執行緒操作解耦,不放在用同一個類中,只有在執行緒操作資源類時,才會建立資源類的物件)

package com.test;

/**
 * 資源類
 * @author Huxudong
 * @createTime 2020-04-01 21:57:39
 **/
public class Resource {
  /** 產品數 */
  private int product = 0;

  /** 進貨 */
  public synchronized void get() {
    if(product >= 10) {
      System.out.println(Thread.currentThread().getName()+":"+"產品已滿!");
      /** 當商品已經滿的時候,進貨執行緒掛起 */
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    /** 進貨 */
    System.out.println(Thread.currentThread().getName()+":"+ ++product);
    /** 喚醒其他執行緒 */
    this.notifyAll();
    
  }

  /** 售貨 */
  public synchronized void sale() {
    if(product <= 0) {
      System.out.println(Thread.currentThread().getName()+":"+"產品已空");
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    /** 售貨 */
    System.out.println(Thread.currentThread().getName()+":"+ --product);
    /** 喚醒其他執行緒 */
    this.notify();
  }
}

然後再建立執行緒來操作我們的資源類(通過java8新特性Lambda表示式直接建立)

package com.test;

import java.util.concurrent.TimeUnit;

/**
 * 執行緒操作資源類,實現執行緒與資源類的解耦合
 * @author Huxudong
 * @createTime 2020-04-01 23:13:54
 **/
public class TestPc {
  public static void main(String[] args) {
    Resource resource = new Resource();
    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        try {
          /** 睡眠,便於觀察結果 */
          TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        resource.get();
      }
    },"生產者A").start();



    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        resource.sale();
      }

    },"消費者C").start();

    new Thread(()->{
      for (int i = 0; i < 20; i++) {

        resource.get();
      }

    },"生產者B").start();

    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        resource.sale();
      }

    },"消費者D").start();
  }
}

先來看看如果使用if條件會發生什麼:

Java wait和notify虛假喚醒原理

對,你沒看錯,怎麼可能會出現負數呢,這肯定是不對的。冷靜下來分析一下,還是有點頭緒,知道哪裡出現了問題的(那你是一個處事不驚的人,很厲害)。

來,分析一下,一開始先呼叫了消費者C,D執行緒(因為我們寫了睡眠在生產者中),消費者此時發現此時product資源為0,所以,消費者C,D這兩個兄弟,沒辦法只能呼叫wait方法,睡眠了,並且釋放了鎖。

但是此時第一個消費者已經甦醒了,發動機開始生產產品了,並且生產之後,又喚醒了所有等待的消費者執行緒。這消費者C,D兩兄弟終於甦醒了,D哥們先獲得了鎖,所以就先消費了一個產品,然後就又發現沒有產品了,又傷心的休眠去了,但是不要忘了,此時還有一個C哥們被喚醒了啊,你喚醒了人家,人家總的乾點什麼事情吧,不然這多難受,剛好不巧的是,此時的判斷條件是if,所以此時C哥們便不受條件的約束,接著上面自己睡眠的程式碼處執行,毅然決然的又去消費了一個產品,原來D哥們消費後,就已經為0了,這個C哥們再去消費減一,不就是-1了嗎,以此類推分析。發現如果判斷條件用不好,此時喚醒的C哥們就相當於虛假喚醒的了,會給程式帶來不可預估的錯誤。所以在這裡判斷必須要使用while,先來看看把if換成while的結果。

Java wait和notify虛假喚醒原理

這回結果就比較正常了,為什麼使用while就可以呢,因為像上文所說,即使喚醒了所有的消費者執行緒,此時會不停while迴圈判斷,如果此時條件是為0,那麼C哥們就不能出while,那麼他也就不回執行下面消費產品的減減操作了,那麼就會避免了這種錯誤。這也是官方提倡的在使用wait 和notifyAll的時候,必須使用while迴圈條件判斷。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。