楠木軒

多線程場景下編碼的一些思考

由 慕容亦凝 發佈於 科技

背景

編寫一個單例的實現。這裏採用一個雙重檢查方式。

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