Java多線程系列之wait

前言

咱們知道讓線程阻塞除了能夠調用sleep方法,join方法還有wait方法,前兩個是屬於Tread的方法,而wait是屬於Object的方法,今天就來聊一聊wait的用法。java


先看一看wait的三個重載方法spa

  • 第一個
public final void wait() throws InterruptedException
  • 第二個
public final void wait(long timeout) throws InterruptedException
  • 第三個
public final void wait(long timeout, int nanos) throws InterruptedException

第一個跟第三個方法最終調用的都是第二個方法,第一個方法至關於調用wait(0),0表示永遠不超時。一旦調用的對象的wait方法,那麼只有調用對象的notify或者notifyAll才能將其喚醒,或者阻塞時間達到了timeout時間也會自動喚醒。線程

每個對象都有一個跟它關聯的monitor,只有獲取到對象的monitor才能調用對象的wait方法和調用對象的notify和notifyAll方法。也就是說wait,notify,notifyAll都必須在對象的synchronized同步方法裏面調用
若是wait沒有在對象的synchronized同步塊裏面執行會拋出3d

java.lang.IllegalMonitorStateException
  • 例如執行如下方法:
private Object lock = new Object();  
  
public void block(){  
        try {  
            lock.wait();  
  } catch (InterruptedException e) {  
            e.printStackTrace();  
  }  
}
  • 拋出異常
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:326)
    at com.bzl.dp.eqpt.dev.TestWait.block(TestWait.java:28)
    at com.bzl.dp.eqpt.dev.TestWait.lambda$main$0(TestWait.java:14)
    at java.base/java.lang.Thread.run(Thread.java:835)
使用notify喚醒wait
  • 看下面例子執行結果是什麼
public static void main(String[] args) {  
  TestWait tw = new TestWait();  
  Thread t1 = new Thread(() -> tw.block());
  t1.start();  
  //短暫休眠以便讓block先執行  
  try {  
        Thread.sleep(10L);  
  } catch (InterruptedException e) {  
        e.printStackTrace();  
  }  
  tw.release();  
}  
  
private Object lock = new Object();  
  
public void block() {  
    synchronized (lock) {  
        try {  
            System.out.println("before wait");  
            lock.wait();  
            System.out.println("after wait");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
  
public void release() {  
    synchronized (lock) {  
        lock.notify();  
        System.out.println("after release");  
    }  
}
  • 運行結果
before wait
after release
after wait

具體流程以下:
首先咱們啓動了一個新的線程執行了lock的wait方法,而後在主線程去喚醒執行了wait的線程,爲了讓block方法先執行,這裏還短暫的休眠了一下子code

  • 1.當t1執行block方法的synchronized塊時,進入lock對象的鎖池,因爲如今lock的monitor尚未其餘線程持有,全部t1搶佔了lock的monitor,打印了before wait,當執行到lock.wait()時,釋放了lock的monitor而且進入lock對象的等待池,等待被喚醒
  • 2.主線程執行release方法,因爲t1已經釋放了lock的monitor,因此當release執行到synchronized塊的時候,進去到lock的鎖池,而且成功的獲取到了lock的monitor,調用lock.notify(),這時把t1從等待池裏喚醒(若是有多個線程在等待池裏,notify只能隨機喚醒一個,須要調用notifyAll才能把等待池的全部線程喚醒),而且進入到lock的鎖池,因爲release方法還沒執行完同步塊代碼因此主線程還持有lock的monitor,因此t1要等lock的monitor的釋放,當主線程打印after release退出同步塊釋放lock的monitor。
  • 3.這時t1才從鎖池成功的獲取到lock的monitor,最後打印after wait

完整流程圖以下:
wait-notify流程對象

使用interrupt()方法中斷wait

除了使用notify或notifyAll,還可使用interrupt方法來中斷wait,當使用interrupt中斷wait時,會拋出InterruptedException異常
代碼以下:blog

public static void main(String[] args) {  
    TestInterrupt tw = new TestInterrupt();  
    Thread t1 = new Thread(() -> tw.block());  
    t1.start();  
    new Thread(() -> tw.interrupt(t1)).start();  
}  
  
private Object lock = new Object();  
  
public void block() {  
    synchronized (lock) {  
        try {  
            System.out.println("before wait");  
            lock.wait();  
            System.out.println("after wait");  
        } catch (InterruptedException e) {  
            System.out.println("Thread is interrupted");  
            e.printStackTrace();  
        }  
    }  
}  
  
public void interrupt(Thread t) {  
    try {  
        Thread.sleep(1000L);  
  } catch (InterruptedException e) {  
        e.printStackTrace();  
  }  
    synchronized (lock) {  
        t.interrupt();  
        try {  
            Thread.sleep(1000L);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Interrupt the thread.");  
    }  
}

運行結果:同步

before wait
Interrupt the thread.
Thread is interrupted
java.lang.InterruptedException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:326)
    at com.bzl.dp.eqpt.dev.TestInterrupt.block(TestInterrupt.java:24)
    at com.bzl.dp.eqpt.dev.TestInterrupt.lambda$main$0(TestInterrupt.java:13)
    at java.base/java.lang.Thread.run(Thread.java:835)

具體流程以下:it

  • 1.建立線程t1,執行block方法,t1進去lock的鎖池,而且獲取lock的monitor,打印before wait, t1釋放lock的monitor,而且進去lock的等待池,等待被喚醒。
  • 2.建立新線程,而且在新線程執行interrupt方法,入參爲t1,新線程休眠1秒,新線程進入lock鎖池而且獲取lock的monitor,執行t1線程的interrupt方法,把t1線程從等待池喚醒,而且進入lock的鎖池,等待獲取lock的monitor,由於如今新線程尚未執行完同步塊,還持有lock的monitor,因此t1線程還在堵塞狀態,當新線程休眠完1秒而且打印Interrupt the thread退出同步塊,釋放monitor。(interrupt方法沒有要求必定要在synchronized塊裏面執行,這裏只是演示t1線程被執行interrupt後須要獲取monitor才能繼續執行,因此這裏加了一個synchronized)
  • 3.t1在鎖池獲取到lock的monitor,由於t1使被調用interrupt喚醒的,因此收到一個InterruptedException異常,而且interrupt狀態會被清空,這時異常被捕獲,跳過打印after wait,最後執行Thread is interrupted

完成流程圖以下:
wait-interrupt流程io

相關文章
相關標籤/搜索