2010年9月14日 星期二

[ Java小學堂 ] 多線程設計模式 : 線程的協調 (wait, notify & notifyAll)

前言 : 

如 多線程設計模式 : 線程的啟動與暫時停止 介紹, 當有一個線程正在執行 synchronized 方法時, 其他線程是無法執行該方法. 這是簡單的互斥共享. 假設我們想做更進一步的處理, 不只是有線程在執行就得乖乖的等, 例如 :
* 若該空間有空閒則寫入數據, 若非空閒則等待到空出為止 (等候) .
* 若該空間已騰出, 則通知正在等候的線程 (通知) .

這是根據 "空間是否有空閒" 為條件進行線程處理. Java 裡有 wait, notify 與 notifyAll 三個方法可以進行處理. wait 是讓線程等候, 而notify 與 notifyAll 是啟動等候中的線程.


wait set - 線程的休息室 : 
在開始學習 wait, notify 與 notifyAll 之前, 先稍微講一下 wait set. 所有的物件都有一個 wait set. wait set 是一個在執行物件的wait 方法時, 操作停止的線程的集合. 它有點類似線程的休息室, 而且每個物件都會有. 一執行 wait方法時, 現程便會停止操作並進入 wait set 這個休息室. 除非發生下列任一種情況, 否則線程便會永遠留在 wait set 裡. 當發生下列任一情況, 線程便會退出 wait set : 
* 有其他線程以 notify 方法喚醒該線程 ;
* 有其他線程以 notifyAll 方法喚醒該線程 ;
* 有其他線程以 interrupt 方法喚醒該線程 ;
* wait 方法已到期.

wait 方法 - 把線程放入 wait set : 
使用 wait 方法時, 現程便進入 wait set. 假設現在執行下面語句 : 
obj.wait(); 

則目前的線程便會停止操作並進入物件 obj 的 wait set. 這個操作稱為線程在 obj 上的 wait. 如果物件方法還有如下語句 : 
wait(); <等同 this.wait();> 

則執行該wait() 的線程就會進入 this 的 wait set. 另外能夠執行 wait() 方法的線程, 必須是獲得 this 的鎖定 (規定). 但是當線程進入 wait set時, 同時便會釋放該物件的鎖定. 請參考下圖說明 : 
 
 

notify 方法 - 從 wait set 拿出線程: 
使用notify 方法時, 可以從 wait set 取出一個線程. 假設現在執行如下語句 : 
obj.notify(); 

則會從物件 obj 的 wait set 裡的線程挑出一個, 並喚醒它. 這個過程如下圖說明 : 
 

 
Ps. 被notify 喚醒的線程並不是在notify 的一瞬間便開始執行. 因為在 notify 的那一刻, 執行notify 的線程還握著鎖定不放, 所以其他線程無法獲得鎖定. 

notifyAll 方法 - 從 wait set 拿出所有線程: 
notify 只喚醒一個線程, 而notifyAll 則是喚醒所有線程, 這是兩者唯一的差別. 請參照下圖 : 
 

補充說明 : 
@. 若沒有被鎖定的線程而去調用 notify, notifyAll 則會拋出 java.lang.IllegalMonitorStateException. 
@. 改用 notify 還是 notifyAll 方法? 
notify 與 notifyAll 方法非常相似, 選擇 notify 因為要喚醒的線程數少, 故程序處理速度相對較快. 但如果部分處理不好, 可能會有程序掛掉的可能. 但若選擇 notifyAll所寫出的程序會比選擇notify 可靠.

@. wait, notify 與 notifyAll 是 Object 類別的方法. 不是Thread 固有的方法. 不過因為Object 類別是所有Java 類的父類, 所以 wait, notify 與 notifyAll 也是 Thread 類的方法.

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...