多線程場景下編碼的一些思考
背景
編寫一個單例的實現。這裏採用一個雙重檢查方式。
image.png
發現是有問題的。主要是編譯優化導致new 對象的順序和可見性問題。
問題修復 :只需要再單例對象 加上 volatile 修飾即可。
分析背後的原因
多線程編程中主要核心關注如下三個點 :
可見性
緩存帶來的原子性 (硬件的坑)
原子性
線程切換帶來的原子性
有序問題
編譯優化,內存模型一般原則(Happens-Before)
image.png
這裏給出一個volatile 修飾的對象 new 對象在編譯後的執行字節碼流程,可以看出沒有做編譯優化和順序重排。
JAVA併發常見的內存模型 valatile :禁
這條規則是指一個valatile 變量的寫操作
Happens-Before 於後續對這個 valatile變量的讀操作
le變量的讀操作
final :
編譯優化更好點 ,生而不變,可以使勁的優化
傳遞性:
Happens-Before: 前面的操作結果對後續的操作是可見的
x = 7 y = 8 在多線程中,
傳遞性 是指 :
線程A:寫 X =7 Y =8 成功
線程B:如果讀到 y = 8 ,
那麼他讀到的X 肯定是7
Java 採用的是管程技術,synchronized 關鍵字及 wait()、notify()、notifyAll() 這三個方法都是管程的組成部分。
而管程和信號量是等價的,所謂等價指的是用管程能夠實現信號量,也能用信號量實現管程。但是管程在利用OOP的封裝特性解決了信號量在工程實踐上的複雜性問題,因此java採用管理機制。
因此java
就是將共享變量及其對共享變量的操作統一封裝起來。在下圖中,管程 X 將共享變量 queue 這個隊列和相關的操作入隊 enq()、出隊 deq() 都封裝起來了;線程 A 和線程 B 如果想訪問共享變量 queue,只能通過調用管程提供的 enq()、deq() 方法來實現;enq()、deq() 保證互斥性,只允許一個線程進入管程。管程模型和麪向對象高度契合的。
image.png
管程如何解決線程間的同步問題呢?
Java 參考了 MESA 模型,語言內置的管程(synchronized)對 MESA 模型進行了精簡。MESA 模型中,條件變量可以有多個,Java 語言內置的管程裏只有一個條件變量。具體如下圖所示。
image.png
JAVA SDK併發包通過Lock 和Condition兩個接口’又’實現了一次管程。LOCK 解決互斥,Condition解決同步問題
重複造輪子
1.能夠響應終端
2.支持超時
3. 非阻塞獲取鎖
image.png
輪子的理由:
1.能夠響應終端
2.支持超時
先找到一個 阻塞的線程 看執行棧信息
image.png
image.png