Java併發程式設計系列之三十二:丟失的訊號
阿新 • • 發佈:2019-02-15
這裡的丟失的訊號是指執行緒必須等待一個已經為真的條件,在開始等待之前沒有檢查等待條件。這種場景其實挺好理解,如果一邊燒水,一邊看電視,那麼在水燒開的時候,由於太投入而沒有注意到水被燒開。丟失的訊號指的就是這種情況。
建立兩個執行緒分別執行通知和等待方法,並且將執行通知的執行緒先與執行等待的執行緒,下面的程式碼演示了這點:
package com.rhwayfun.patchwork.concurrency.r0414;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Created by rhwayfun on 16-4-14.
*/
public class MissedNotifyDemo {
//持有的鎖
private static Object lock = new Object();
//日期格式器
private static final DateFormat format = new SimpleDateFormat("HH:mm:ss");
//等待執行緒執行的方法
public void waitMethod() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": enter waitMethod at "
+ format.format(new Date()));
synchronized (lock){
//呼叫wait方法執行等待
System.out.println(Thread.currentThread().getName() + ": start invoke wait method at "
+ format.format(new Date()));
lock.wait();
System.out.println(Thread.currentThread().getName() + ": end invoke wait method at "
+ format.format(new Date()));
}
System.out.println(Thread.currentThread().getName() + ": exit waitMethod at "
+ format.format(new Date()));
}
//通知執行緒執行的方法
public void notifyMethod(){
System.out.println(Thread.currentThread().getName() + ": exit notifyMethod at "
+ format.format(new Date()));
synchronized (lock){
//呼叫通知的方法
System.out.println(Thread.currentThread().getName() + ": start invoke notify method at "
+ format.format(new Date()));
lock.notifyAll();
System.out.println(Thread.currentThread().getName() + ": end invoke notify method at "
+ format.format(new Date()));
}
System.out.println(Thread.currentThread().getName() + ": exit notifyMethod at "
+ format.format(new Date()));
}
static class WaitThread implements Runnable{
private MissedNotifyDemo missedNotifyDemo;
public WaitThread(MissedNotifyDemo missedNotifyDemo) {
this.missedNotifyDemo = missedNotifyDemo;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(1000);
missedNotifyDemo.waitMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class NotifyThread implements Runnable{
private MissedNotifyDemo missedNotifyDemo;
public NotifyThread(MissedNotifyDemo missedNotifyDemo) {
this.missedNotifyDemo = missedNotifyDemo;
}
@Override
public void run() {
try {
//休眠的時間必須要小於等待執行緒的休眠時間
TimeUnit.MILLISECONDS.sleep(500);
missedNotifyDemo.notifyMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
MissedNotifyDemo missedNotifyDemo = new MissedNotifyDemo();
new Thread(new WaitThread(missedNotifyDemo),"WaitThread").start();
new Thread(new NotifyThread(missedNotifyDemo),"NotifyThread").start();
}
}
執行結果如下:
WaitThread因為丟失了來自NotifyThread的通知而一直陷入等待中。當然,這裡僅僅是演示了這種情況,在實際的例子中,執行等待的執行緒都需要一個等待條件,為了避免出現丟失的訊號,仍然需要對條件變數進行while迴圈的判斷。
關於等待通知機制的補充
- 每當在等待一個條件時,一定要確保在條件變數變為真的時候才發出喚醒的通知
- 在呼叫wait/notify/notifyAll方法時,必須首先獲得鎖
- 每次呼叫完wait方法,獲得鎖就會自動釋放
- 呼叫notify時,JVM從等待佇列中的一個執行緒進行喚醒,呼叫notifyAll時,將等待佇列中所有執行緒都喚醒
- 只有同時滿足兩個條件時才能使用notify:一是所有等待執行緒的型別都相同,這就是說,等待佇列只與一個條件變數相關,並且所有的執行緒在喚醒後執行的都是相同的操作;二是單進單出,也就是說在條件變數的每個通知,要求只能最多喚醒一個執行緒