在前面講解synchronize的文章中,有提到wait和notify,大概描述了它的使用,這裡我將根據官方api詳細的教你如何使用。
所屬物件
wait,notify,notifyAll 是定義在Object類的例項方法,用於控制執行緒狀態。
文件分析
我們找到Object類,下載它的文件,翻譯每個方法的註釋。
總結如下:
-
wait() 和 notify() 必須由物件持有者去呼叫,有三種方式: 1️⃣執行該物件的synchronized例項方法 2️⃣執行synchronized程式碼塊 3️⃣執行該類的synchronized靜態方法
-
當想要呼叫wait( )進行執行緒等待時,必須要取得這個鎖物件的控制權(物件監視器),一般是放到synchronized(obj)程式碼中。
-
在while迴圈裡用wait操作效能更好(比if判斷)
-
呼叫obj.wait( )釋放了obj的鎖,否則其他執行緒也無法獲得obj的鎖,也就無法在synchronized(obj){ obj.notify() } 程式碼段內喚醒A。
-
notify( )方法只會通知等待佇列中的第一個相關執行緒(不會通知優先順序比較高的執行緒)
-
notifyAll( )通知所有等待該競爭資源的執行緒(也不會按照執行緒的優先順序來執行)
-
如果是synchronized宣告的方法,wait()操作後會施放synchronized鎖,相反notify()觸發後會重拿起synchronized鎖。
-
如果當前執行緒不是當前物件所持有,則會報異常IllegalMonitorStateException
例項
1. 通過呼叫物件的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() {
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的呼叫物件。
2. 通過synchronized修飾方法來實現
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的同步鎖,執行完該方法後才會釋放鎖。