JAVA語言規範-執行緒和鎖章節之同步、等待和通知
原文連結 本文是Oracle官方《Java語言規範》的譯文
JAVA語言規範:執行緒和鎖
1 同步
JAVA程式語言提供了執行緒間通訊的多種機制。這些方法中最基本的是同步化,此方法是使用監視器實現的。JAVA中每個物件與一個監視器相關聯,一個執行緒可以加鎖和解鎖監視器。一次僅有一個執行緒可能在監視器上持有鎖。嘗試鎖住該監視器的任何其他執行緒被阻塞,直到它們可以再該監視器上獲得一個鎖。執行緒t可以多次鎖住特別的監視器;每個解鎖將一個加鎖操作的作用反轉來了。
synchronized語句計算了一個物件的引用;然後它嘗試在該物件的監視器上執行加鎖操作,並不進一步繼續,直到鎖操作已經成功完成。在加鎖操作被執行完後,會執行synchronized語句體。如果語句體的執行沒有完成(正常或突然)。那麼將在相同的監視器上自動執行相同的解鎖操作。
JAVA程式語言沒有防止,也沒有要求檢查死鎖條件。執行緒在多個物件上(直接或間接)持有鎖的程式,應該使用傳統技術來避免死鎖,建立不會死鎖的高階加鎖原語(如果有必要的話)。其他機制(比如讀寫java.util.concurrent 包中的 volatile 變數和類)提供了一些同步的其他方法。
2 等待集合 和 通知
JAVA中的每個物件,都有一個關聯的監視器,也會有一個關聯的等待集合。等待集合是一個執行緒的集合。
當物件第一次被建立時,它的等待集合為空,增加或移除該集合中的執行緒,這樣的簡單操作都是原子性的。等待集合受到以下方法的操縱: Object.wait, Object.notify, and Object.notifyAll.。
等待集合的操作也受到執行緒中斷狀態的的影響,還有Thread類中那些可以進行中斷執行緒方法的影響。此外在Thread類中的sleep和join的方法,它們能夠獲得等待集合和通知的動作。
2.1等待(Wait)
等待操作由wait()方法的執行引起,或者也可以由限定時間長度的wait()方法觸發。
呼叫方法wait(long millisecs)或者函式wait(long millisecs, int nanosecs) ,當呼叫的引數均為0時,這樣的呼叫等同於wait()。
如果執行緒在沒有丟擲InterruptedException 的情況下返回,那麼執行緒就從wait 正常返回。
令執行緒t是在物件m上執行等待方法的執行緒,並令n是t在m上加鎖操作的編號,這些操作已經不被解鎖操作匹配。下面的操作之一發生:
- 如果n是0(也就是,執行緒t已經沒有佔用目標m的鎖)則丟擲IllegalMonitorStateException 異常。
- 如果這是一個定時等待,並且十億分之一秒引數不在0-999999,或者毫秒引數是負的,那麼就會丟擲IllegalArgumentException 的異常。
- 如果執行緒t被中斷,那就丟擲InterruptedException,並將t的中斷狀態設定為假。
否則,下面的序列發生:
1、執行緒t被新增到物件m的等待集合中,並在m上執行n個解鎖操作。
2、執行緒t沒有執行任何的進一步指令,直到它已經從m的等待集合中刪除。由於下面的操作的任何之一,該程式可能從等待集合中刪除,並在後面的某個時間繼續。
- 在等待等待集合中刪除而選擇的t的m上正在執行的notify操作。
- 在m上正被執行的notifyAll操作。
- 在t上執行的interrupt操作。
- 如果這是一個定時等待,則為m的等待集合刪除的內部操作,該集合至少在millisecs毫秒加nanosecs十億分之一秒消逝後發生(從寫操作開始)。
- 根據實現的內部操作。實現被允許(儘管不鼓勵)執行“偽造的喚醒”——以便從等待集合中刪除中刪除執行緒,從而能夠在沒有顯式指令這樣做的情況下再繼續。注意這個裝備成了在迴圈內使用wait的JAVA編碼實踐的必要條件,這些迴圈只有在執行緒等待持有的某個邏輯條件時才終止。
每個執行緒必須通過可能導致它從等待集合中刪除的時間確定順序。順序不一定與其他的排序一直,但執行緒表現得像以那個迴圈發生的那些事件一樣。
例如,如果執行緒t在m的等待佇列中,然後t的中斷和m的通知發生,那麼在這些事件上必須有一個順序。如果中斷被認為首先發生,那麼t將最終通過丟擲InterruptedException來從wait中中返回,而且中的等待集合的某個其他執行緒(如果在通知的時候存在的話)必須接收通知。如果通知被認為是首先發生的,那麼t將最終正常地wait返回,且中斷仍然掛起。
- 執行緒t在m上執行n個加鎖操作。
- 如果由於中斷執行緒t在步驟2中從m的等待集合中刪除了,那麼t的中斷狀態就被設定為假,並且等待方法丟擲InterruptedException。
2.2 通知(notify)
通知操作在呼叫方法notify和notifyAll 呼叫之後發生。令執行緒t是執行物件m上的這些方法的任一方法的執行緒,並令n是t在m上的加鎖操作的數量,這些操作沒有被解鎖操作匹配。下面操作之一發生了。
- 如果n是0,則丟擲IllegalMonitorStateException。情形是這樣的:執行緒t已經沒有佔有目標m的鎖。
- 如果n大於0,並且這是一個notify操作,那麼如果m的等待集合不是空的,則是m的當前等待集合的一個成員的執行緒u被選擇,並從等待集合中刪除(不保證在等待集合中選定哪個執行緒)。從等待集合中進行該刪除讓u在等待操作中得以繼續。但注意,繼續之後的u的加鎖操作不能成功,直到t完全解鎖m的監視器後的某個時間。
- 如果n大於0,並且這是一個notifyAll操作,那麼所有的執行緒就從m的等待集合中刪除並繼續,但請注意。它們當中僅有的一個將一次鎖住wait的繼續期間需要的監視器
2.3 中斷
中斷操作在呼叫方法Thread.interrupt及定義來依次呼叫它的方法(比如ThreadGroup.interrupt)之後發生。對於某個執行緒u,令t是呼叫u.interrupt的執行緒,其中t和u可能是相同的。此操作導致u的中斷狀態被設定為真。
另外,如果存在著等待集合包含u的某個物件m,那麼u就從m的等待集合中刪除。這使得u能夠在等待操作中繼續,在該操作的情況中,此等待將在重新加鎖m的監視器後丟擲InterruptedException。
呼叫Thread.isInterrupted可以確定執行緒的中斷狀態。靜態方法Thread.interrupted有執行緒呼叫來觀察和清除自己的中斷狀態。
2.4 等待、通知和中斷的互動
上面的規範允許我們確定於等待、通知和中斷有關的幾個屬性。如果在等待時,執行緒同時是通知和中斷的,它就可能是下面之一:
- 從wait正常返回,儘管仍然有掛起中斷(在其他工作中,對Thread.interrupted的呼叫將返回真)。
- 通過丟擲InterruptedException從wait處返回。
執行緒不可以重置它的中斷狀態,並從wait的呼叫中正常返回。
同樣,通知不能由於中斷而丟失。假定執行緒的集合s在物件m的等待集合中,並且另一個執行緒在m上執行notify,那麼有下面之一發生:
- 至少s中有一個執行緒從wait處正常返回,或者
- s中的所有執行緒必須通過丟擲InterruptedException退出wait。
注意,如果執行緒通過notify被中斷和喚醒,並且執行緒通過丟擲InterruptedException從wait返回,那麼等待集合中的某個執行緒必須被通知到。