JAVA多线程中wait()方法的详细分析
轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/zhaoyanjun6/article/details/119645679
本文出自【趙彥軍的博客】
文章目錄
- wait 和 notify 簡(jiǎn)介
- 對(duì)象控制權(quán)(monitor)
- 解題
- 發(fā)散:notify()和notifyAll()
- 擴(kuò)展
最近看帖子,發(fā)現(xiàn)一道面試題:
啟動(dòng)兩個(gè)線程, 一個(gè)輸出 1,3,5,7…99, 另一個(gè)輸出 2,4,6,8…100 最后 STDOUT 中按序輸出 1,2,3,4,5…100
題目要求用 Java 的 wait + notify 機(jī)制來(lái)實(shí)現(xiàn),重點(diǎn)考察對(duì)于多線程可見(jiàn)性的理解。
wait 和 notify 簡(jiǎn)介
wait 和 notify 均為 Object 的方法:
- Object.wait() —— 暫停一個(gè)線程
- Object.notify() —— 喚醒一個(gè)線程
從以上的定義中,我們可以了解到以下事實(shí):
- 想要使用這兩個(gè)方法,我們需要先有一個(gè)對(duì)象 Object。
- 在多個(gè)線程之間,我們可以通過(guò)調(diào)用同一個(gè)對(duì)象的wait()和notify()來(lái)實(shí)現(xiàn)不同的線程間的可見(jiàn)。
對(duì)象控制權(quán)(monitor)
在使用 wait 和 notify 之前,我們需要先了解對(duì)象的控制權(quán)(monitor)。在 Java 中任何一個(gè)時(shí)刻,對(duì)象的控制權(quán)只能被一個(gè)線程擁有。如何理解控制權(quán)呢?請(qǐng)先看下面的簡(jiǎn)單代碼:
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();} }直接執(zhí)行,我們將會(huì)得到以下異常:
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)出錯(cuò)的代碼在:object.wait();。這里我們需要了解以下事實(shí):
- 無(wú)論是執(zhí)行對(duì)象的 wait、notify 還是 notifyAll 方法,必須保證當(dāng)前運(yùn)行的線程取得了該對(duì)象的控制權(quán)(monitor)
- 如果在沒(méi)有控制權(quán)的線程里執(zhí)行對(duì)象的以上三種方法,就會(huì)報(bào) java.lang.IllegalMonitorStateException 異常。
- JVM 基于多線程,默認(rèn)情況下不能保證運(yùn)行時(shí)線程的時(shí)序性
在上面的示例代碼中,我們 new 了一個(gè) Thread,但是對(duì)象 object 的控制權(quán)仍在主線程里。所以會(huì)報(bào) java.lang.IllegalMonitorStateException 。
我們可以通過(guò)同步鎖來(lái)獲得對(duì)象控制權(quán),例如:synchronized 代碼塊。對(duì)以上的示例代碼做改造:
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();} }再次執(zhí)行,代碼不再報(bào)錯(cuò)。
我們可以得到以下結(jié)論:
- 調(diào)用對(duì)象的wait()和notify()方法,需要先取得對(duì)象的控制權(quán)
- 可以使用synchronized (object)來(lái)取得對(duì)于 object 對(duì)象的控制權(quán)
解題
了解了對(duì)象控制權(quán)之后,我們就可以正常地使用 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); //偶數(shù)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); //奇數(shù)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); //偶數(shù)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); //奇數(shù)ob.notifyAll();ob.wait();}} catch (InterruptedException e) {e.printStackTrace();}}}}).start();} }發(fā)散:notify()和notifyAll()
這兩個(gè)方法均為 native 方法,在JDK 1.8 中的關(guān)于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 控制權(quán)下的一個(gè)處于 wait 狀態(tài)的線程。若有多個(gè)線程處于此 object 控制權(quán)下的 wait 狀態(tài),只有一個(gè)會(huì)被喚醒。
也就是說(shuō),如果有多個(gè)線程在 wait 狀態(tài),我們并不知道哪個(gè)線程會(huì)被喚醒。
在JDK 1.8 中的關(guān)于notifyAll()的JavaDoc如下:
Wakes up all threads that are waiting on this object’s monitor.
譯為:
喚醒所有處于此 object 控制權(quán)下的 wait 狀態(tài)的線程。
所以,我們需要根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景來(lái)考慮如何使用。
擴(kuò)展
關(guān)于對(duì)象鎖,notify只是喚醒一個(gè)線程B,B這個(gè)線程要等到當(dāng)前對(duì)象釋放synchronized的對(duì)象鎖才能執(zhí)行,也就是A flag.wait()才能執(zhí)行。再用高級(jí)點(diǎn)的說(shuō)法,就是notify是等待池進(jìn)入鎖池。
wait()方法 表示持有對(duì)象鎖的線程A準(zhǔn)備釋放對(duì)象鎖權(quán)限,釋放CPU資源并進(jìn)入等待。
Wait() 和notify() 方法只能從synchronized方法或塊中調(diào)用,需要在其他線程正在等待的對(duì)象上調(diào)用notify方法。
notifyAll 通知JVM喚醒所有競(jìng)爭(zhēng)該對(duì)象鎖的線程,線程A synchronized 代碼作用域結(jié)束后,JVM通過(guò)算法將對(duì)象鎖權(quán)限指派給某個(gè)線程X,所有被喚醒的線程不再等待。線程X synchronized 代碼作用域結(jié)束后,之前所有被喚醒的線程都有可能獲得該對(duì)象鎖權(quán)限,這個(gè)由JVM算法決定。
通俗比喻:
有兩個(gè)人A和B都要和一個(gè)女孩G約會(huì)(A線程和B線程都調(diào)用synchronized約會(huì)方法,鎖對(duì)象G.class)。
A率先和G約會(huì)了(A線程進(jìn)入synchronized方法),B只能等著。
A在約會(huì)途中被電話叫走,把G涼在那里(調(diào)用G.class.wait())。
這時(shí)候B發(fā)現(xiàn)G沒(méi)人約會(huì)了,于是上場(chǎng)(B線程進(jìn)入synchronized方法)。
等到B和G約會(huì)完成,B又打電話叫A回來(lái)繼續(xù)約會(huì)(B線程調(diào)用G.class.notify()方法),A才回來(lái)繼續(xù)約會(huì)直到完成。
notifyAll()就是不止A和B了,可能有更多的人要和G約會(huì),等A走了之后某個(gè)人上場(chǎng)。
總結(jié)
以上是生活随笔為你收集整理的JAVA多线程中wait()方法的详细分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android NDK学习笔记6:异常处
- 下一篇: Android 应用目录分析