在前面講解synchronize的文章中,有提到wait和notify,大概描述了它的使用,這裏我將根據官方api詳細的教你如何使用。java
wait,notify,notifyAll 是定義在Object類的實例方法,用於控制線程狀態。api
咱們找到Object類,下載它的文檔,翻譯每一個方法的註釋。bash
總結以下:jvm
wait() 和 notify() 必須由對象持有者去調用,有三種方式: 1️⃣執行該對象的synchronized實例方法 2️⃣執行synchronized代碼塊 3️⃣執行該類的synchronized靜態方法ide
當想要調用wait( )進行線程等待時,必需要取得這個鎖對象的控制權(對象監視器),通常是放到synchronized(obj)代碼中。性能
在while循環裏用wait操做性能更好(比if判斷)測試
調用obj.wait( )釋放了obj的鎖,不然其餘線程也沒法得到obj的鎖,也就沒法在synchronized(obj){ obj.notify() } 代碼段內喚醒A。ui
notify( )方法只會通知等待隊列中的第一個相關線程(不會通知優先級比較高的線程)this
notifyAll( )通知全部等待該競爭資源的線程(也不會按照線程的優先級來執行)spa
若是是synchronized聲明的方法,wait()操做後會施放synchronized鎖,相反notify()觸發後會重拿起synchronized鎖。
若是當前線程不是當前對象所持有,則會報異常IllegalMonitorStateException
/** 調用對象的 wait 和 notify 實例
* Created by Fant.J.
*/
public class Demo {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("修改flag線程執行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.setFlag(true);
notify();
System.out.println("修改flag並釋放鎖成功");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (demo.isFlag() != true){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("flag爲true時線程執行");
}
}).start();
}
}
修改flag線程執行
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.thread.waitNotify.Demo$2.run(Demo.java:41)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.thread.waitNotify.Demo$1.run(Demo.java:31)
at java.lang.Thread.run(Thread.java:748)
複製代碼
從運行結果能夠看出,它報錯IllegalMonitorStateException,咱們上面有給出報該異常的緣由,是由於沒有沒有獲取到對象的監視器控制權,咱們new了兩個線程,一個調用了wait 一個調用了notify,jvm認爲wait是一個線程下的wait,notify是另外一個線程下的notify,事實上,咱們想實現的是針對Demo對象的鎖的wait和notify,因此,咱們須要調用Demo對象的wait和notify方法。
修改後的代碼:
/** 調用對象的 wait 和 notify 實例
* Created by Fant.J.
*/
public class Demo {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (demo) {
System.out.println("修改flag線程執行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.setFlag(true);
demo.notify();
System.out.println("修改flag並釋放鎖成功");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (demo) {
while (demo.isFlag() != true) {
try {
demo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("flag爲true時線程執行");
}
}
}).start();
}
}
修改flag線程執行
修改flag並釋放鎖成功
flag爲true時線程執行
複製代碼
修改了兩處,一處是加了synchronized代碼塊,一處是添加了wait和notify的調用對象。
package com.thread.waitNotify_1;
/** 經過synchronized方法實現 wait notify
* Created by Fant.J.
*/
public class Demo2 {
private volatile boolean flag = false;
public synchronized boolean getFlag() {
System.out.println(Thread.currentThread().getName()+"開始執行...");
if (this.flag != true){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"執行結束...");
return flag;
}
public synchronized void setFlag(boolean flag) {
this.flag = flag;
notify();
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
Runnable target1 = new Runnable() {
@Override
public void run() {
demo2.getFlag();
}
};
Runnable target2 = new Runnable() {
@Override
public void run() {
demo2.setFlag(true);
}
};
new Thread(target1).start();
new Thread(target1).start();
new Thread(target1).start();
new Thread(target1).start();
}
}
Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
複製代碼
爲何四個線程都執行了呢?synchronized不是鎖定線程了嗎?我在上面8點中也有說明,wait()操做後,會暫時釋放synchronized的同步鎖,等notify()觸發後,又會重拾起該鎖,保證線程同步。
而後咱們條用target2來釋放一個線程:
new Thread(target1).start();
new Thread(target1).start();
new Thread(target1).start();
new Thread(target1).start();
new Thread(target2).start();
Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
Thread-0執行結束...
複製代碼
能夠看到只釋放了一個線程,而且是第一個線程,若是有優先級,他也是釋放第一個線程。
若是把notify改爲notifyAll。
Thread-0開始執行...
Thread-2開始執行...
Thread-1開始執行...
Thread-3開始執行...
Thread-3執行結束...
Thread-1執行結束...
Thread-2執行結束...
Thread-0執行結束...
複製代碼
如何證實,每次notify後會拿到synchronized鎖呢,我在執行notify後添加一些時間戳捕獲幫助咱們查看
public synchronized void setFlag(boolean flag) {
this.flag = flag;
// notify();
notifyAll();
System.out.println("測試notify觸發後會不會等2s"+System.currentTimeMillis());
try {
Thread.sleep(2000);
System.out.println("測試notify觸發後會不會等2s"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
測試notify觸發後會不會等2s1529817196847
測試notify觸發後會不會等2s1529817198847
Thread-3執行結束...
Thread-2執行結束...
Thread-1執行結束...
Thread-0執行結束...
複製代碼
能夠看到的確是notify重拾了synchronized的同步鎖,執行完該方法後纔會釋放鎖。