天天看點

并發——wait/notify/join/park

文章目錄

      • API介紹
      • 原理
      • 使用:
        • 正确的使用姿勢
      • join原理
      • Park & Unpark
        • 線程狀态

API介紹

  • obj.wait()

    讓進入 object 螢幕的線程到 waitSet 等待
  • obj.notify()

    在 object 上正在 waitSet 等待的線程中挑一個喚醒
  • obj.notifyAll()

    讓 object 上正在 waitSet 等待的線程全部喚醒

原理

并發——wait/notify/join/park
  • Owner 線程發現條件不滿足,調用 wait 方法,即可進入 WaitSet 變為 WAITING 狀态
  • BLOCKED 和 WAITING 的線程都處于阻塞狀态,不占用 CPU 時間片
  • BLOCKED 線程會在 Owner 線程釋放鎖時喚醒
  • WAITING 線程會在 Owner 線程調用 notify 或 notifyAll 時喚醒,但喚醒後并不意味者立刻獲得鎖,仍需進入 EntryList 重新競争

使用:

它們都是線程之間進行協作的手段,都屬于 Object 對象的方法。必須獲得此對象的鎖,才能調用這幾個方法

final static Object obj = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("執行....");
                try {
                    obj.wait(); // 讓線程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代碼....");
            }
        }).start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("執行....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代碼....");
            }
        }).start();

        sleep(2);
        log.debug("喚醒 obj 上其它線程");
        synchronized (obj) {
            obj.notify(); 
 } 
           

補充:

wait(long n)

有時限的等待, 到 n 毫秒後結束等待,或是被 notify

sleep(long n)

wait(long n)

的差別

  1. sleep 是 Thread 方法,而 wait 是 Object 的方法
  2. sleep 不需要強制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用
  3. sleep 在睡眠的同時,不會釋放對象鎖的,但 wait 在等待的時候會釋放對象鎖
  4. 它們 狀态 TIMED_WAITING

正确的使用姿勢

由于虛假喚醒的問題,可以使用一個while來優化

模闆:

synchronized(lock) { 

   while(條件不成立) {   
        lock.wait();    
    }  
    // 幹活 
}
 
//另一個線程
 synchronized(lock) {   
  	lock.notifyAll();
 }
           

join原理

public final void join() throws InterruptedException {
        join(0);
    }

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
        //線程存活,就一直等
            while (isAlive()) {
                wait(0);
            }
        } else {
        //有時限的等待
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
           

Park & Unpark

必須注意的是,隻能先park在unpark

底層不是java可見的

使用方法

Thread t1 = new Thread(() -> {
        log.debug("start...");
        sleep(1);
        log.debug("park...");
        LockSupport.park();
        log.debug("resume...");
    }, "t1"); t1.start();

    sleep(2); 
    log.debug("unpark..."); 
    LockSupport.unpark(t1);
           

原理:

使用park

  1. 目前線程調用 Unsafe.park() 方法
  2. 檢查 _counter ,本情況為 0,這時,獲得 _mutex 互斥鎖
  3. 線程進入 _cond 條件變量阻塞
  4. 設定 _counter = 0
并發——wait/notify/join/park

使用unpark

  1. 調用 Unsafe.unpark(Thread_0) 方法,設定 _counter 為 1
  2. 喚醒 _cond 條件變量中的 Thread_0
  3. Thread_0 恢複運作
  4. 設定 _counter 為 0
并發——wait/notify/join/park

從原理中看出為什麼不能先用unpark,因為會使标志位變為1,下一次park就停不住

線程狀态

并發——wait/notify/join/park
  • NEW --> RUNNABLE

    當調用 t.start() 方法時,由 NEW --> RUNNABLE

  • RUNNABLE <–> WAITING

    t 線程用 synchronized(obj) 擷取了對象鎖後

    • 調用 obj.wait() 方法時,t 線程從 RUNNABLE --> WAITING
    • 調用 obj.notify() , obj.notifyAll() , t.interrupt() 時 ,隻是讓他們恢複競争而已
      • 競争鎖成功,t 線程從 WAITING --> RUNNABLE
      • 競争鎖失敗,t 線程從 WAITING --> BLOCKED
  • RUNNABLE <–> WAITING
    • 目前線程調用 t.join() 方法時,目前線程從 RUNNABLE --> WAITING
      • 注意是目前線程在t 線程對象的螢幕上等待
    • t 線程運作結束,或調用了目前線程的 interrupt() 時,目前線程從 WAITING --> RUNNABLE