jsr133-第一二章
1:介紹
java虛擬機支持多線程運行。線程代表的就是Thread class。對用戶來說創建線程的唯一辦法就是創建一個Thread對象;每一個線程都和一個Thread對象關聯。Thread對象調用start()方法就啟動了相應的線程。
線程的表現,尤其是當不能正常同步的時候,會變得混亂和違反直覺。這個規范說明(指:jsr133)描述了java語言里多線程編寫的語義定義;它包含了這樣的規則:即定義共享內存的讀線程能夠讀到的被多個寫線程寫入的哪些數據。盡管這個規范類似于不同硬件架構的內存模型,但這是一個java內存模型的語義定義(semantics?)。
這些語義并不是描述一個多線程程序是如何執行的。而是描述了多線程程序允許展現的行為。(the behaviors that multithreaded programs are allowed to exhibit.)
(Any execution strategy that generates only allowed behaviors is an acceptable execution strategy. )任何執行策略只要產生的是這個規范允許的行為那么它就是可以接受的執行策略。
1.1 Locks
多線程之間的溝通有很多種機制。最基礎的機制就是同步(synchronization),使用monitors來實現的機制。每個對象關聯一個監聽器(monitor),一個線程可以鎖定或者解鎖它。同一時間只有一個線程可以持有一個監視器的鎖。任何其他試圖鎖定已經被鎖定的監視器的線程都會被阻塞直到他們持有這個監視器的鎖。
一個線程t可以鎖定一個特定的監聽器很多次;每一個解鎖操作對應一次鎖定操作,還原對象狀態。
同步聲明(同步塊)計算出一個對象的引用;然后嘗試執行一個對該對象監視器的鎖定操作并且直到鎖定完成之前不會繼續執行。鎖定操作執行后,同步聲明中的操作才會被執行。如果同步塊的執行完成,不管是正常結束還是異常終端,同一個監視器鎖的解鎖操作都會自動執行。
一個同步方法在調用時會自動執行一個鎖定操作;在鎖定操作完成之前同步方法的內容不會被執行。如果這個方法是一個實例方法,它鎖定的是它被調用實例關聯的監視器(monitor)(也就是:方法體執行過程中被稱為this的這個對象)。如果是靜態方法,它鎖定的就是方法聲明所在的類的類對象對應的監視器。同樣的,一旦方法體執行結束,不管是正常的還是異常終止,解鎖操作都會在同一個監聽器上自動執行。
這份聲明文檔(指本文)既不提供也不需要檢測死鎖的條件。那些多線程持有(直接或間接)多個對象的鎖的程序應該使用常用的避免死鎖的方法,如果有必要的話,就創建更級別的鎖原語。
其他機制,例如讀取和寫入Volatile以及在java.util.concurrent 包中的類,提供了正確同步的可選方法。
1.2 Notation in Examples
Java內存模型并不是根本上基于java語言的面相對象特性的。為了例子的簡潔、簡易,我們只展示代碼片段而忽略類和方法的定義,或者明確的非關聯性。大多數例子是由兩個或多個包括訪問本地變量,共享全局變量或者一個對象的字段實例的狀態的線程組成。我們一般使用類似r1或者r2這樣的變量名來表明一個方法或者一個線程的本地變量。這些變量是不能夠被其他線程訪問的。
?2:Incorrectly Synchronized Programs Exhibit Surprising Behav-iors 錯誤的同步程序表現出的違反直覺的行為
Java語法允許編譯器和cpu以最優的性能執行,這樣與錯誤的同步代碼相互影響,就會產生一些看上去匪夷所思的行為。
想一下,例如圖1所示。這個程序使用本地變量r1和r2 以及 共享變量A和B。 看上去結果應該是:r2 ==2, r1==1 。直覺上,第1步和第3步應該最先執行。如果第1步先執行,那么它就看不到第四部的寫操作。如果第3步先執行了,它就看不到第2步執行的寫操作。
如果一些執行表現出這樣的行為,那么我們就知道步驟4先于步驟1發生,步驟1先于步驟2,步驟2先于步驟3,步驟3先于步驟4。事實上,這是很荒謬的。
但是,編譯器允許不影響線程獨立運行的情況下,對任何一個線程進行重排序。如果步驟1和步驟2進行了重排序,那么r2 == 2和r1 == 1 這樣的結果,是不是就有可能發生了。
對于一些程序來說,這樣的行為也許看上去是“broken”。但是,需要被標注的是這個代碼是被恰當的同步:
* ?一個線程里對一個變量有一個寫操作
* ?另一個線程對同一個變量的讀操作
* ?并且這個讀操作和寫操作并沒有按順序同步
當這樣的情況發生時,就被叫做 a data race。(譯:一個數據競爭)。當代碼包含一個數據競爭,就會經常發生一些違反直覺的情況。
一些機器可能會產生如圖1那樣的執行順序。just-in-time compiler(即時編譯器)和處理器可能會重排序代碼。另外,運行的虛擬機的內存層級架構也可能使程序出現看上去好像重排序一樣的情況。為了簡單起見,我們會簡單的像編譯器那樣涉及到任何能夠使代碼重排序的東西。源代碼到字節碼的轉換可能重排序和轉變程序,但是必須按照這個指定的規范執行。
另一個結果異常的例子可以看下圖2。這個程序也是同步錯誤的;在訪問變量過程中,它沒有任何約束的任何順序訪問共享內存。
一個常見編譯器優化設計到讀取r2的值給r5:因為他們都讀取了r1.x的值,并且之間沒有任何其他相關的寫操作。
現在考慮這樣一種情況,分配給線程2的r6.x的賦值操作介于線程1的第一次r1.x和r3.x的讀取。如果編譯器決定復用r2給r5,那么r2和r5的將會是0,同時r4將會是3。從這個程序的遠景來看,p.x的值已經從0修改成了3,然后又改回來了。
盡管這樣的結果看上去很驚訝,但事實上這是被大多JVM實現鎖允許的行為。但同時,被JLS和JVMS這樣的傳統的虛擬機內存模型所禁止:這是第一個舊的JMM(Java內存模型: Java Memory Model)需要被替換的標志。
?3:Informal Semantics
當代碼被重排序的時候,一個程序必須被正確的同步來避免多種類型的違反直覺的行為發生。使用正確的同步不能保證程序里上述的行為是正確的。但是,使用它允許一個程序員以一種簡單的途徑來推理出一個程序的可能行為;一個正確同步的程序的行為是極少依賴可能的重排序的。沒有正確的同步,非常奇怪的、令人迷惑的和匪夷所思的行為就可能會出現。
有兩個關鍵的辦法來理解一個程序是否正確同步了:
1:Conflicting Accesses (訪問沖突)
兩個訪問(讀取或者寫入)同一個共享字段或者數組元素,如果其中至少有一個訪問時寫入那么就被稱為沖突Conficting。
2:Happens-Before Relationship?
兩個行為如果是happens-before關系,可以排序。如果一個行為happens-before另一個行為
轉載于:https://www.cnblogs.com/aquariusm/p/6072531.html
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的jsr133-第一二章的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决国内NPM安装依赖速度慢问题
- 下一篇: 【Alpha】Daily Scrum M