學到了什么?
線程與鎖模型的三個主要危害:
- 競態條件:即代碼行為取決于個操作的時序;
- 死鎖:當需要持有多把鎖時,如果獲取鎖的順序不一樣,則有可能死鎖;
- 內存可見性:編譯器、JVM以及硬件都有可能進行重排序,導致線程看到錯誤的值。同時,緩存的存在可能導致線程看不到另一個線程的修改。
避免危害的準則:
- 對共享變量的所有訪問都進行同步;
- 讀線程和寫線程都進行同步;
- 按照約定的全局順序來獲取多把鎖;
- 當持有鎖時避免調用外星方法(就是自己無法控制的方法);
- 持有鎖的時間應該盡可能的短。
自習
- 閱讀William Pugh的網站:Java內存模型;
這個網站像是一個目錄,列舉了各種與Java內存模型有關的資料地址。 - 自學JSR 133(Java內存模型)的FAQ;
學習成果:http://www.lxweimin.com/p/145e8b98c509 - Java內存模型是如何保證對象初始化是線程安全的?是否必須通過枷鎖才能在線程之間安全的公開對象?
對象初始化的線程安全問題其實就是由于對象還未初始化完就賦值給某個全局引用,然后被其他線程操作。但只要屬性都是final的,JMM就保證在將對象賦值給某個引用前,final屬性一定會被初始化。但如果不全是final屬性,那就需要通過volatile來解決了,但加了volatile后,變量的讀取性能會下降。
是否一定需要加鎖才能在線程之間安全的公開對象,按我的理解,如果對象是真正的不可變的,那就不需要。 - 了解反模式“雙重檢查鎖模式”(double-checked locking)以及為什么稱其為反模式。
雙重檢查鎖模式,顧名思義,就是進行了兩次檢查,并加鎖,而且是加在第二次檢查的時候(這個顧名思義不出來)。它是來實現一個能延遲初始化的線程安全的單例模式。雙重檢查就是進行了兩次非空檢查,鎖就是在第二次檢查的時候枷鎖。
之所以被稱為反模式就是因為上面討論的,對象的初始化不一定是線程安全的。在單例對象還未初始化完成的時候,就被賦值給了全局的單例引用,另一個線程獲取單例的時候,由于第一次檢查沒有加鎖,所以就可以得到這個還未完全初始化的對象。
實踐
- 對于哲學家進餐問題,用最開始有死鎖隱患的代碼做一些實驗。嘗試修改哲學家思考狀態的時長、進餐狀態的時長以及進餐的人數。這些變量對于出現死鎖的時機有什么影響?設想我們正在進行調試,那應該如何增大出現死鎖的幾率?
運行了三次才出現死鎖(半小時、一小時、五小時)。將人數調整為三人后,運行了兩次出現死鎖(一小時、兩個半小時),調整為一人運行了一個小時即出現了死鎖。將時間調成50毫秒,很快就出現了死鎖。由此可見,人數越少,時間越短越容易死鎖。其實想想也是,死鎖本質可以看成一個概率問題,人數越少意味著死鎖的環越小,也就是死鎖的概率大,時間越短則意味著頻率增大,這樣概率大,頻率大,復現的時間當然短。所以,當我們在調試時,也可以從增加縮小死鎖的環,以及增加運行頻率這兩方面入手。 - (困難)編寫一段程序,在不使用同步的前提下,模擬內存寫操作的亂序執行。這個任務之所以有難度,是因為Java內存模型可能不會優化過于簡單的例子,故找到這個優化場景比較困難。