文章目錄
- 前言
- 1. suspend / resume
- 正常執行
- 出現死鎖
- 2. wait / notify
- 正常執行
- 出現死鎖
- 3. park / unpark
- 正常執行
- 出現死鎖
- 三種等待/通知機制的比較
前言
想要實作對個執行緒之間的協同(如:執行緒的執行順序),就涉及到執行緒之間的通信,執行緒通信有許多種方式,比如,共享變數、訊息佇列、檔案共享等等,
下面,我們就一起來看看JDK api中提供的一種執行緒通信的方式——等待/通知機制,
jdk提供了三種實作執行緒通信的等待/通知機制:
- suspend / resume
- wait / notify
- park / unpark
生產者與消費者模式是一個典型的多執行緒并發協作的模式,在這個模式中,一部分執行緒被用于去生產資料,另一部分執行緒去處理資料,下面就是用等待/通知機制來實作生產者消費者模式,

注意:在下面的例子中,主執行緒充當生產者,new Thread()的執行緒充當消費者的角色,
1. suspend / resume
呼叫suspend掛起目標執行緒,通過resume可以恢復執行緒執行,
正常執行
/** 臨界資源 */
public static Object resource = null;
public void suspendResumeTest() throws Exception {
// 啟動執行緒
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
System.out.println(new Date() + " 進入等待............");
Thread.currentThread().suspend();
}
System.out.println("獲取到臨界資源,開始運行");
});
consumerThread.start();
// 3秒之后,生產一個資源
Thread.sleep(3000L);
resource = new Object();
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
consumerThread.resume();
}
然而,suspend / resume這種等待/通知方式已經被棄用了,因為它非常容易寫出死鎖,表現在兩個方面:
- suspend不會自動釋放鎖,從而導致死鎖;
- 如果suspend 和 resume 的執行順序搞反了就會導致程式永久掛起,從而形成死鎖,
出現死鎖
下面舉例說明兩種死鎖的形成:
- suspend不會自動釋放鎖
/** 臨界資源 */
public static Object resource = null;
public void suspendResumeDeadLockTest() throws Exception {
// 啟動執行緒
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
System.out.println(new Date() + " 進入等待............");
// 當前執行緒拿到鎖,然后掛起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("獲取到臨界資源,開始運行");
});
consumerThread.start();
// 3秒之后,生產一個資源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
// 爭取到鎖以后,再恢復consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
}
運行程式,發現程式被卡住了,執行緒一直被掛起,發生死鎖,

- suspend 和 resume 的執行順序不對
/** 臨界資源 */
public static Object resource = null;
public void suspendResumeDeadLockTest2() throws Exception {
// 啟動執行緒
Thread consumerThread = new Thread(() -> {
if (resource == null) {
System.out.println(new Date() + " 進入等待............");
try { // 為這個執行緒加上一點延時
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 這里的掛起執行在resume后面
Thread.currentThread().suspend();
}
System.out.println("獲取到臨界資源,開始運行");
});
consumerThread.start();
// 3秒之后,生產一個臨界資源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
consumerThread.resume();
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
consumerThread.join();
}
suspend 和 resume 的執行順序搞反了就會導致程式永久掛起,從而形成死鎖,

已經生產了臨界資源被通知消費者執行緒,但程式一起掛起不再運行,
2. wait / notify
wait / notify 是 suspend / resume機制的一個替代機制,
wait方法導致當前執行緒等待,加入該物件的等待集合中,并且放棄當前持有的物件鎖,也就是wait方法會自動釋放鎖,這樣就減小了寫出死鎖的可能性,
wait / notify方法只能由同一物件鎖的持有者執行緒呼叫,也就是寫在同步塊里面,否則會拋出lllegalMonitorStateException例外,
正常執行
/** 臨界資源 */
public static Object resource = null;
public void waitNotifyTest() throws Exception {
// 啟動執行緒
new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
synchronized (this) {
try {
System.out.println(new Date() + " 進入等待............");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("獲取到臨界資源,開始運行");
}).start();
// 3秒之后,生產一個臨界資源
Thread.sleep(3000L);
resource = new Object();
synchronized (this) {
this.notifyAll();
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
}
}

出現死鎖
雖然wait會自動解鎖,但是對順序有要求,如果在notify被呼叫之后,才開始wait方法的呼叫,執行緒會永遠處于WAITING狀態,
public void waitNotifyDeadLockTest() throws Exception {
// 啟動執行緒
new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println(new Date() + " 進入等待............");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("獲取到臨界資源,開始運行");
}).start();
// 3秒之后,生產一個臨界資源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
synchronized (this) {
this.notifyAll();
System.out.println(" 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
}
}

3. park / unpark
執行緒呼叫park則等待“許可”,unpark方法為指定執行緒提供“許可(permit)”,
不要求park和unpark方法的呼叫順序,
多次呼叫unpark之后,再呼叫park,執行緒會直接運行,但不會疊加,也就是說,連續多次呼叫park方法,第一次會拿到“許可”直接運行,后續呼叫會進入等待,
正常執行
public void parkUnparkTest() throws Exception {
// 啟動執行緒
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
System.out.println(new Date() + " 進入等待............");
LockSupport.park();
}
System.out.println("獲取到臨界資源,開始運行");
});
consumerThread.start();
// 3秒之后,生產一個臨界資源
Thread.sleep(3000L);
resource = new Object();
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
LockSupport.unpark(consumerThread);
}

出現死鎖
park并不會釋放鎖,所有再同步代碼中使用可能會出現死鎖
public void parkUnparkDeadLockTest() throws Exception {
// 啟動執行緒
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果沒有可用的臨界資源,則進入等待
System.out.println(new Date() + " 進入等待............");
// 當前執行緒拿到鎖,然后掛起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("獲取到臨界資源,開始運行");
});
consumerThread.start();
// 3秒之后,生產一個臨界資源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
// 爭取到鎖以后,再恢復consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println(new Date() + " 等待三秒生產了一個臨界資源,通知消費者>>>>>>>>>>>>");
}

三種等待/通知機制的比較
| suspend / resume | wait / notify | park/unpark |
|---|---|---|
| 已棄用 | - | - |
| suspend不釋放鎖 | wait釋放鎖 | park不釋放鎖 |
| 要求suspend / resume執行順序 | 要求wait / notify執行順序 | 不要求park/unpark執行順序 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/404126.html
標籤:java
