JAVA多线程中wait()方法的详细分析
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/119645679
本文出自【趙彥軍的博客】
文章目錄
- wait 和 notify 簡介
- 對象控制權(monitor)
- 解題
- 發散:notify()和notifyAll()
- 擴展
最近看帖子,發現一道面試題:
啟動兩個線程, 一個輸出 1,3,5,7…99, 另一個輸出 2,4,6,8…100 最后 STDOUT 中按序輸出 1,2,3,4,5…100
題目要求用 Java 的 wait + notify 機制來實現,重點考察對于多線程可見性的理解。
wait 和 notify 簡介
wait 和 notify 均為 Object 的方法:
- Object.wait() —— 暫停一個線程
- Object.notify() —— 喚醒一個線程
從以上的定義中,我們可以了解到以下事實:
- 想要使用這兩個方法,我們需要先有一個對象 Object。
- 在多個線程之間,我們可以通過調用同一個對象的wait()和notify()來實現不同的線程間的可見。
對象控制權(monitor)
在使用 wait 和 notify 之前,我們需要先了解對象的控制權(monitor)。在 Java 中任何一個時刻,對象的控制權只能被一個線程擁有。如何理解控制權呢?請先看下面的簡單代碼:
public class Util {public void run(){Object ob = new Object();new Thread(new Runnable() {@Overridepublic void run() {try {ob.wait();} catch (InterruptedException e) {e.printStackTrace();}}}).start();} }直接執行,我們將會得到以下異常:
E/AndroidRuntime: FATAL EXCEPTION: Thread-3Process: com.example.myapplication, PID: 12211java.lang.IllegalMonitorStateException: object not locked by thread before wait()at java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:442)at java.lang.Object.wait(Object.java:568)at com.example.myapplication.Util$1.run(Util.java:20)at java.lang.Thread.run(Thread.java:929)出錯的代碼在:object.wait();。這里我們需要了解以下事實:
- 無論是執行對象的 wait、notify 還是 notifyAll 方法,必須保證當前運行的線程取得了該對象的控制權(monitor)
- 如果在沒有控制權的線程里執行對象的以上三種方法,就會報 java.lang.IllegalMonitorStateException 異常。
- JVM 基于多線程,默認情況下不能保證運行時線程的時序性
在上面的示例代碼中,我們 new 了一個 Thread,但是對象 object 的控制權仍在主線程里。所以會報 java.lang.IllegalMonitorStateException 。
我們可以通過同步鎖來獲得對象控制權,例如:synchronized 代碼塊。對以上的示例代碼做改造:
public class Util {public void run(){Object ob = new Object();new Thread(new Runnable() {@Overridepublic void run() {synchronized (ob){try {ob.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}).start();} }再次執行,代碼不再報錯。
我們可以得到以下結論:
- 調用對象的wait()和notify()方法,需要先取得對象的控制權
- 可以使用synchronized (object)來取得對于 object 對象的控制權
解題
了解了對象控制權之后,我們就可以正常地使用 notify 和 wait 了,下面給出我的解題方法,供參考。
方法一:
public class Util {public void run(){Object ob = new Object();new Thread(new Runnable() {@Overridepublic void run() {synchronized (ob){try {for (int i = 0 ;i < 100; i=i+2){ob.notify();Log.d("num--", "" + i); //偶數ob.wait();}} catch (InterruptedException e) {e.printStackTrace();}}}}).start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (ob){try {for (int i = 1 ;i < 100; i=i+2){ob.notify();Log.d("num------", "" + i); //奇數ob.wait();}} catch (InterruptedException e) {e.printStackTrace();}}}}).start();} }方法二:
public class Util {public void run(){Object ob = new Object();new Thread(new Runnable() {@Overridepublic void run() {synchronized (ob){try {for (int i = 0 ;i < 100; i=i+2){Log.d("num--", "" + i); //偶數ob.notifyAll();ob.wait();}} catch (InterruptedException e) {e.printStackTrace();}}}}).start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (ob){try {for (int i = 1 ;i < 100; i=i+2){Log.d("num------", "" + i); //奇數ob.notifyAll();ob.wait();}} catch (InterruptedException e) {e.printStackTrace();}}}}).start();} }發散:notify()和notifyAll()
這兩個方法均為 native 方法,在JDK 1.8 中的關于notify()的JavaDoc如下:
Wakes up a single thread that is waiting on this object’s monitor. If any threads are waiting on this object, one of them is chosen to be awakened.
譯為:
喚醒此 object 控制權下的一個處于 wait 狀態的線程。若有多個線程處于此 object 控制權下的 wait 狀態,只有一個會被喚醒。
也就是說,如果有多個線程在 wait 狀態,我們并不知道哪個線程會被喚醒。
在JDK 1.8 中的關于notifyAll()的JavaDoc如下:
Wakes up all threads that are waiting on this object’s monitor.
譯為:
喚醒所有處于此 object 控制權下的 wait 狀態的線程。
所以,我們需要根據實際的業務場景來考慮如何使用。
擴展
關于對象鎖,notify只是喚醒一個線程B,B這個線程要等到當前對象釋放synchronized的對象鎖才能執行,也就是A flag.wait()才能執行。再用高級點的說法,就是notify是等待池進入鎖池。
wait()方法 表示持有對象鎖的線程A準備釋放對象鎖權限,釋放CPU資源并進入等待。
Wait() 和notify() 方法只能從synchronized方法或塊中調用,需要在其他線程正在等待的對象上調用notify方法。
notifyAll 通知JVM喚醒所有競爭該對象鎖的線程,線程A synchronized 代碼作用域結束后,JVM通過算法將對象鎖權限指派給某個線程X,所有被喚醒的線程不再等待。線程X synchronized 代碼作用域結束后,之前所有被喚醒的線程都有可能獲得該對象鎖權限,這個由JVM算法決定。
通俗比喻:
有兩個人A和B都要和一個女孩G約會(A線程和B線程都調用synchronized約會方法,鎖對象G.class)。
A率先和G約會了(A線程進入synchronized方法),B只能等著。
A在約會途中被電話叫走,把G涼在那里(調用G.class.wait())。
這時候B發現G沒人約會了,于是上場(B線程進入synchronized方法)。
等到B和G約會完成,B又打電話叫A回來繼續約會(B線程調用G.class.notify()方法),A才回來繼續約會直到完成。
notifyAll()就是不止A和B了,可能有更多的人要和G約會,等A走了之后某個人上場。
總結
以上是生活随笔為你收集整理的JAVA多线程中wait()方法的详细分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android NDK学习笔记6:异常处
- 下一篇: Kotlin by属性委托