volatile與synchronized實現原理
阿新 • • 發佈:2018-12-18
------------------------------------------------------------------
剛開始認識volatile的時候,覺得對它的一些特性非常迷惑。比如:具有可見性,如果一個執行緒修改了volatile變數的值,那麼其它執行緒也會發現這一點;同時它又不具有原子性,多個執行緒對被volatile修飾的int 變數累加會造成相互覆蓋。這我就迷糊了:不是一個執行緒修改了,其它的執行緒中資料都無效了麼,既然會重新讀取,為啥最終還會相互覆蓋呢?
volatile原理:
我們知道:如果一個欄位被宣告成volatile,java執行緒記憶體模型確保所有執行緒看到這個變數的值是一致的。這個就是所謂的“可見性”,就是一個執行緒修改了,其他執行緒能知道這個操作,這就是可見性。如何實現的呢?volatile修飾的變數在生成彙編程式碼的時候,會產生一條lock指令,lock字首的指令在多核處理器下會引發兩件事情:
1、將當前處理器快取航的資料寫回到系統記憶體;
2、這個寫回記憶體的操作會使得在其它cpu裡快取了該記憶體地址的資料無效;
這個使得其它cpu裡資料無效又是怎麼實現的呢?
cpu處理資料速度是很快的,為了提高處理速度,充分發揮cpu效能,cpu不直接跟記憶體進行通訊,而是先將資料讀入cpu快取記憶體後再進行操作,但操作完不知道何時回寫到記憶體。如果對聲明瞭volatile的變數進行寫操作,jvm就會向處理器傳送一條lock字首指令,將這個變數所在快取行的資料寫回到系統記憶體。但就算寫回到記憶體,如果其它處理器快取的還是舊值,再執行計算操作就會有問題。所以多處理器下,為了保證各個處理器的快取是一致的,就有了一個“快取一致性協議”,所有硬體廠商都要按照這個標準來生產硬體。具體就是每個處理器通過嗅探在總線上傳播的資料來檢查自己快取的值是不是過期了,當處理器發現自己快取行對應的記憶體地址被修改,就會將當前處理器的快取行設定為無效狀態,當處理器對這個資料進行修改操作的時候,會重新從系統記憶體中把資料讀到處理器快取。
注意,如果該資料已經在別的處理器執行緒被修改過了,只是沒有重新整理到記憶體,則這時候是不會重新讀資料的,而是等一下直接重新整理到記憶體,這就造成了覆蓋的事情發生 ;別的執行緒重新讀取資料僅僅是在將變數讀到了cpu快取,還沒有使用的時候才有的,一旦使用了,即使發現被修改了,也不會重新讀取重新計算。具有可見性,而又多執行緒不安全的問題就是這樣產生的。
synchronized原理:
synchronized是用java的monitor機制來實現的,就是synchronized程式碼塊或者方法進入及退出的時候會生成monitorenter跟monitorexit兩條命令。執行緒執行到monitorenter時會嘗試獲取物件所對應的monitor所有權,即嘗試獲取的物件的鎖;monitorexit即為釋放鎖。
monitor機制是跟java物件結構相關的。HotSpot虛擬機器中,物件在記憶體中儲存的佈局可以分為三塊區域:物件頭,例項資料跟對齊填充。
從上面的這張圖裡面可以看出,物件在記憶體中的結構主要包含以下幾個部分:
- Mark Word(標記欄位):物件的Mark Word部分佔4個位元組,其內容是一系列的標記位,比如輕量級鎖的標記位,偏向鎖標記位等等。
- Klass Pointer(Class物件指標):Class物件指標的大小也是4個位元組,其指向的位置是物件對應的Class物件(其對應的元資料物件)的記憶體地址
- 物件實際資料:這裡麵包括了物件的所有成員變數,其大小由各個成員變數的大小決定,比如:byte和boolean是1個位元組,short和char是2個位元組,int和float是4個位元組,long和double是8個位元組,reference是4個位元組
- 對齊:最後一部分是對齊填充的位元組,按8個位元組填充。
- 其實,如果是陣列物件,頭資訊還包括一個Array length的內容,用來記錄陣列長度。