4.12多線程--wait/notify

故事引入

image.png

image.png

wait / notify 原理

image.png
  • Owner 線程發(fā)現(xiàn)條件不滿足,調(diào)用 wait 方法,即可進入 WaitSet 變?yōu)?WAITING 狀態(tài)
    WaitSet 里的線程是獲取過鎖 又放棄了,EntryList 中的是還沒有獲得鎖
  • BLOCKED 和 WAITING 的線程都處于阻塞狀態(tài),不占用 CPU 時間
  • BLOCKED 線程會在 Owner 線程釋放鎖時被喚醒
  • WAITING 線程會在 Owner 線程調(diào)用 notify 或 notifyAll 時喚醒,但并不意味著會立即獲得鎖,仍然會進入 EntryList 重新競爭

wait / notify API

  • obj.wiat() 讓進入 obj 監(jiān)視器的線程到 WaitSet 等待
  • obj.notify() 選中 obj 監(jiān)視器的 WaitSet 中的某個線程被喚醒
  • obj.notifyAll() obj 監(jiān)視器的 WaitSet 中的全部線程都被喚醒
    他們都是線程之間進行協(xié)作的手段,都屬于 obj 對象的方法。必須獲得此對象的鎖,才能調(diào)用這兩個方法。(線程只有變成 obj 對象的監(jiān)視器的 Owner 才能調(diào)用)
/**
  * 1、wait / notify / notifyAll
  * 2、帶一個參數(shù)的 wait :等待設定時間后,如果沒有被其他線程喚醒,就不等了,繼續(xù)向下執(zhí)行;如果還沒到等待時間,另一個線程來喚醒,就被喚醒,繼續(xù)向下執(zhí)行;
  * 3、帶兩個參數(shù)的 wait:wait(毫秒,納秒),不會精確到納秒,多一毫秒;
 */
public class BiasedDemo5 {
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                System.out.println("線程 t1 開始執(zhí)行");
                try {
                    lock.wait();
                    // lock.wait(1000); // 即使沒有其他線程喚醒,1s 后 繼續(xù)執(zhí)行后續(xù)代碼
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程 t1 完成其他業(yè)務");
            }
        },"t1").start();

        new Thread(()->{
            synchronized (lock){
                System.out.println("線程 t2 開始執(zhí)行");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程 t2 完成其他業(yè)務");
            }
        },"t2").start();

        Thread.sleep(2000);
        synchronized (lock){
//            lock.notify(); // 只喚醒一個
            lock.notifyAll();// 全部喚醒,全部執(zhí)行
        }
    }
}

wait / notify 的正確使用姿勢(step1-5)

sleep(long n) 和 wait(long n) 的區(qū)別
1)sleep 是 Thread 的靜態(tài)方法,wait 是 Object的方法
2)sleep 不需要強制和 synchronized 配合使用,但 wait 必須和 synchronized 聯(lián)合使用,獲取對象鎖
3)如果 sleep 和 synchronized 聯(lián)合使用,在 sleep 的時候不會釋放對象鎖,但 wait 在等待是會釋放對象鎖
4)sleep 和 wait 都不占用 CPU 時間,狀態(tài)都是 TIMED_WAITING

public class BiasedDemo6 {
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                System.out.println("線程 t1 開始執(zhí)行");
                try {
//                    lock.wait();
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程 t1 完成其他業(yè)務");
            }
        },"t1").start();

        Thread.sleep(1000);
        synchronized (lock){
            System.out.println("主線程獲得鎖,開始執(zhí)行");
        }
    }
}
/**
 *  STEP 0(使用 wait / notify 前)
 **/
public class BiasedDemo7 {
    final static Object room = new Object();
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
                if (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[t1], 沒有煙");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[t1], 有煙,開始干活");
                }
            }
        },"t1").start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    System.out.println(sdf.format(new Date()) + " 線程[其他],開始干活");
                }
            },"其他").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            // synchronized (room){
                hasCigarette = true;
                System.out.println(sdf.format(new Date()) + " 線程[送煙的],來送煙了");
            // }
        },"送煙的").start();
    }
}

輸出:(注意看前邊的時間哈)

05:33:58 線程[t1], 有沒有煙?false
05:33:58 線程[t1], 沒有煙
05:33:59 線程[送煙的],來送煙了
05:34:00 線程[t1], 有沒有煙?true
05:34:00 線程[t1], 有煙,開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
  • 其他干活的線程,都要一直阻塞,效率太低
  • 線程 t1 必須睡足 2s 后才能醒來,就算煙提前送到,也無法立刻醒來
  • 加了 synchronized(room) 后,就好比 t1 在里面反鎖了門睡覺,煙根本沒法送進門,main 線程沒加 synchronized 就好像 main 線程是翻窗戶進來的
  • 解決方法:使用 wait - notify 機制
/**
 *  STEP 1(使用 wait / notify)
 **/
public class BiasedDemo8 {
    final static Object room = new Object();
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
                if (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[t1], 沒有煙");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[t1], 有煙,開始干活");
                }
            }
        },"t1").start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    System.out.println(sdf.format(new Date()) + " 線程[其他],開始干活");
                }
            },"其他").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
                System.out.println(sdf.format(new Date()) + " 線程[送煙的],來送煙了");
                room.notify();
            }
        },"送煙的").start();
    }
}

輸出

05:51:00 線程[t1], 有沒有煙?false
05:51:00 線程[t1], 沒有煙
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:01 線程[送煙的],來送煙了
05:51:01 線程[t1], 有沒有煙?true
05:51:01 線程[t1], 有煙,開始干活
  • 解決了其他線程被阻塞的問題
  • 但如果有其他線程也在等待條件呢?

虛假喚醒:

/**
 *  STEP 2(錯誤喚醒/虛假喚醒)
 **/
public class BiasedDemo9 {
    final static Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 線程[小南], 有沒有煙?" +hasCigarette);
                if (!hasCigarette){ //while (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[小南], 沒有煙,等一會");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 線程[小南], 有沒有煙?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 線程[小南], 有煙,開始干活");
                }
            }
        },"小南").start();

        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 線程[小女], 有沒有外賣?" +hasTakeout);
                if (!hasTakeout){//while (!hasTakeout){
                    System.out.println(sdf.format(new Date()) + " 線程[小女], 沒有外賣,等一會");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 線程[小女], 有沒有外賣?" +hasTakeout);
                if(hasTakeout){
                    System.out.println(sdf.format(new Date()) + " 線程[小女], 有外賣,開始干活");
                }
            }
        },"小女").start();


        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                System.out.println(sdf.format(new Date()) + " 線程[送貨的],來送外賣了");
                room.notify();
                //room.notifyAll();
            }
        },"送貨的").start();
    }
}

輸出:

06:02:46 線程[小南], 有沒有煙?false
06:02:46 線程[小南], 沒有煙,等一會
06:02:46 線程[小女], 有沒有外賣?false
06:02:46 線程[小女], 沒有外賣,等一會
06:02:47 線程[送貨的],來送外賣了
06:02:47 線程[小女], 有沒有外賣?true
06:02:47 線程[小女], 有外賣,開始干活
06:02:47 線程[小南], 有沒有煙?false
//線程1
synchronized(room){
  while(條件不成立){
    room.wait();
  }
  // 干活
}
// 線程2 
synchronized(room){
   room.notifyAll();
}
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容