1. 程式人生 > >Java併發程式設計系列之三十二:丟失的訊號

Java併發程式設計系列之三十二:丟失的訊號

這裡的丟失的訊號是指執行緒必須等待一個已經為真的條件,在開始等待之前沒有檢查等待條件。這種場景其實挺好理解,如果一邊燒水,一邊看電視,那麼在水燒開的時候,由於太投入而沒有注意到水被燒開。丟失的訊號指的就是這種情況。

建立兩個執行緒分別執行通知和等待方法,並且將執行通知的執行緒先與執行等待的執行緒,下面的程式碼演示了這點:

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迴圈的判斷。

關於等待通知機制的補充

  1. 每當在等待一個條件時,一定要確保在條件變數變為真的時候才發出喚醒的通知
  2. 在呼叫wait/notify/notifyAll方法時,必須首先獲得鎖
  3. 每次呼叫完wait方法,獲得鎖就會自動釋放
  4. 呼叫notify時,JVM從等待佇列中的一個執行緒進行喚醒,呼叫notifyAll時,將等待佇列中所有執行緒都喚醒
  5. 只有同時滿足兩個條件時才能使用notify:一是所有等待執行緒的型別都相同,這就是說,等待佇列只與一個條件變數相關,並且所有的執行緒在喚醒後執行的都是相同的操作;二是單進單出,也就是說在條件變數的每個通知,要求只能最多喚醒一個執行緒