天天看點

JAVA語言規範-線程和鎖章節之同步、等待和通知

java程式設計語言提供了線程間通信的多種機制。這些方法中最基本的是同步化,此方法是使用螢幕實作的。java中每個對象與一個螢幕相關聯,一個線程可以加鎖和解鎖螢幕。一次僅有一個線程可能在螢幕上持有鎖。嘗試鎖住該螢幕的任何其他線程被阻塞,直到它們可以再該螢幕上獲得一個鎖。線程t可以多次鎖住特别的螢幕;每個解鎖将一個加鎖操作的作用反轉來了。

synchronized語句計算了一個對象的引用;然後它嘗試在該對象的螢幕上執行加鎖操作,并不進一步繼續,直到鎖操作已經成功完成。在加鎖操作被執行完後,會執行synchronized語句體。如果語句體的執行沒有完成(正常或突然)。那麼将在相同的螢幕上自動執行相同的解鎖操作。

java程式設計語言沒有防止,也沒有要求檢查死鎖條件。線程在多個對象上(直接或間接)持有鎖的程式,應該使用傳統技術來避免死鎖,建立不會死鎖的進階加鎖原語(如果有必要的話)。其他機制(比如讀寫java.util.concurrent 包中的 volatile 變量和類)提供了一些同步的其他方法。

java中的每個對象,都有一個關聯的螢幕,也會有一個關聯的等待集合。等待集合是一個線程的集合。

當對象第一次被建立時,它的等待集合為空,增加或移除該集合中的線程,這樣的簡單操作都是原子性的。等待集合受到以下方法的操縱: object.wait, object.notify, and object.notifyall.。

等待集合的操作也受到線程中斷狀态的的影響,還有thread類中那些可以進行中斷線程方法的影響。此外在thread類中的sleep和join的方法,它們能夠獲得等待集合和通知的動作。

等待操作由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。

通知操作在調用方法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的繼續期間需要的螢幕

中斷操作在調用方法thread.interrupt及定義來依次調用它的方法(比如threadgroup.interrupt)之後發生。對于某個線程u,令t是調用u.interrupt的線程,其中t和u可能是相同的。此操作導緻u的中斷狀态被設定為真。

另外,如果存在着等待集合包含u的某個對象m,那麼u就從m的等待集合中删除。這使得u能夠在等待操作中繼續,在該操作的情況中,此等待将在重新加鎖m的螢幕後抛出interruptedexception。

調用thread.isinterrupted可以确定線程的中斷狀态。靜态方法thread.interrupted有線程調用來觀察和清除自己的中斷狀态。

上面的規範允許我們确定于等待、通知和中斷有關的幾個屬性。如果在等待時,線程同時是通知和中斷的,它就可能是下面之一:

從wait正常傳回,盡管仍然有挂起中斷(在其他工作中,對thread.interrupted的調用将傳回真)。

通過抛出interruptedexception從wait處傳回。

線程不可以重置它的中斷狀态,并從wait的調用中正常傳回。

同樣,通知不能由于中斷而丢失。假定線程的集合s在對象m的等待集合中,并且另一個線程在m上執行notify,那麼有下面之一發生:

至少s中有一個線程從wait處正常傳回,或者

s中的所有線程必須通過抛出interruptedexception退出wait。

注意,如果線程通過notify被中斷和喚醒,并且線程通過抛出interruptedexception從wait傳回,那麼等待集合中的某個線程必須被通知到。