重新認識volatile

2021-10-12 01:56:02 字數 3045 閱讀 4921

併發程式設計:肯定是為了更合理充分的利用多核cpu架構的效能

cpu 主頻率遠遠高於主存,所以引入cpu快取,cpu載入資料無法繞過快取,而且對於多核cpu,一級快取不是共享的,二級和**快取是執行緒共享的!cpu計算的資料並不是直接從主存中去拿,而且通過層層在快取中尋找。

下圖是乙個單cpu多核心的架構圖:

先看這樣一段**:

載入過程就是threada通過read指令讀取到flag,在通過load指令載入到快取,也就是jmm中的本地記憶體,cpu再通過暫存器去使用flag,threadb也是同樣的流程,通過assign指令為本地記憶體中的flag賦值,再寫入主存:

由於while(true)並沒有釋放時間片,所以在這裡可以讓它去進行上下文切換,則就會有時間清除快取

兩次的執行結果肯定很簡單

什麼情況下的上下文切換不會清除快取呢?可以設定乙個非常小的休眠時間

這個時候,設定500納秒的sleep和500000納秒的等待會導致結果完全不一樣!!

這與cpu多核心架構有關係,cpu修改之後的值並不會立即重新整理到主存,這便導致了快取不一致的問題,這是屬於cpu架構的問題,不僅僅存在於jvm層面,只要是這種的cpu架構都會出現這種問題,所以首先得靠加鎖來解決這種問題,在早期有一種東西叫做匯流排鎖:

一旦發生資料修改的回寫操作,直接把匯流排加鎖,這樣別的執行緒在使用資料的時候就不得不重新從主存得到新的資料,而且存在嚴重的效能問題,如果存在大量io操作時,還使用這種匯流排鎖,那麼肯定io效能肯定會下降!

於是出現了快取一致性協議:

主要用到的快取一致性協議時mesi協議(m修改、e獨佔、s共享、i無效四種狀態,這四種狀態記錄的是快取行的轉態)。

這裡還有乙個機制叫做匯流排嗅探機制,嗅探通過匯流排的資料,如果只有乙個核心用到,那麼就會給這個快取行乙個狀態叫做e狀態(獨佔狀態),當另乙個執行緒也用到了這個變數的時候,cpu就會通過廣播機制通知其他的核心把這個快取行的狀態修改為s狀態(共享狀態),接下來其中乙個執行緒對這個變數進行了修改,那麼對於這個執行緒來說,這個變數的快取行變成了m狀態(修改狀態),回寫的時候會通過匯流排,匯流排嗅探機制嗅探到這個變數的快取行已經是m狀態,它就會通過其他的核心,說快取無效,則其他核心上的快取行就會變成i狀態(無效狀態),變成無效狀態後如果還需要使用這個變數,那麼肯定只能重新從主存中去載入這個變數到快取,而且必須等待修改核心修改(回寫主存)完成!

這裡鎖的快取行最大值為 64 byte ,匯流排鎖主要是用到了lock原語

jmm描述的是一種抽象的概念,一組規則,通過這組規則控制程式中各個變數在共享資料區域和私有資料區域的訪問方式,jmm是圍繞原子性、有序性、可見性展開的

這個jmm模型只是乙個抽象的概念,圖上畫出的也只是邏輯空間,通過對應到硬體記憶體架構是這樣的:

在上面的例子中,我們明顯可以通過volatile關鍵字來解決這個問題,那麼volatile又是如何實現的呢?

通過彙編指令來看看就知道了,執行時加虛擬機器執行引數:

-xx:+unlockdiagnosticvmoptions -xx:+printassembly -xcomp

先看看如何保證可見性的:

底層實現了 lock addl $0x0,(%rsp) ,觸發了快取一致性協議

什麼是重排序呢?是指編譯器生成了指令序列,處理亂序執行!

接下來看看指令重排序的乙個例子:

列印出來(0,0)(0,1)(1,0)(1,1)都有

這就好比單例模式,請看下面這個例子:

myinstance = new singletonfactory(),這句話主要是三個指令構成的,如果不能保證指令有序性的話,那麼拿到的物件就是未被初始化的物件,是無效的!

對於x86架構的cpu,加上volatile寫後面的storeload記憶體屏障,我們也可以手動加上記憶體屏障:

這個加上記憶體屏障的方法定義是 public native void storefence(); 因此是原生實現,可以看看openjdk的虛擬機器實現,可以看到對於64位機器使用的是rsp暫存器,對於非64位用的是esp暫存器

併發程式設計的三大特性:

可見性、有序性、原子性

volatile保證可見性與有序性,但是不能保證原子性,要保證原子性需要借助 synchronized、lock鎖機制,同理也能保證有序性與可見性,因為 synchronized和lock能夠保證任一時刻只有個執行緒訪問該**塊。關於volatile不保證原子性這個其實很好證明:

因為執行緒進行了很多次無效計算,所以結果並不是100000,他們都是從主存中拿的值,而且值都是對的,但是計算過程卻不是原子的,準確的說應該是資源並沒有被鎖定,導致自己修改的時候別人也在修改,所以正確理解volatile的作用是很重要的!

最後看看cas操作對應的彙編指令:

賞 謝謝你請我喝咖啡

支付寶

重新認識container

我還清楚的記得,第一次從 那兒聽說container這個詞 結果他給我解釋了半天還是似懂非懂的。今天,偷閒翻了下posa4,發現裡面對container的解釋特別清楚。粗略的理解下來是,為了分離關注點,而實現的對系統資源的封裝。豁然開朗的發現,os就是應用程式的container。突發奇想的,開發乙...

重新認識測試

以前總覺得測試是軟體開發的邊緣職位,開發人員才是軟體生命週期的核心人員。隨著對網際網路公司的了解,逐步了解到測試的重要性。以bat為例,三家公司均設定了測試開發工程師崗位,該崗位的主要職責就是編寫自動化測試案例,通過對 的邏輯進行分析,設計出能夠覆蓋大部分 的測試用例。如阿里的測試開發工程師的崗位描...

重新認識ARC

雖然用了很久的arc,感受了 簡潔。但是對arc底層實現並不了解。今天抽空研究了下,做些簡單地總結。一 strong 1.區域性變數 對於區域性變數來說,在超出作用域的地方由編譯器自動插入release。大概轉化為 在非arc返回的autorelease型別的方法 在blog手碼大概 如有錯誤還望指...