Java多線程volatile和synchronized總結


volatile是輕量級的synchronized,在多處理器(多線程)開發中保證了共享變量的“可見性”。可見性表示當一個線程修改了一個共享變量時,另外一個線程能讀到這個修改的值。正確的使用volatile,能比synchronized的使用和執行成本更低,因為它不會引起線程上下文的切換和調度。使用時只需要把字段聲明成volatile即可。
 
處理器實現,有volatile變量修飾的共享變量進行寫操作的時候會出現LOCK前綴指令。觸發兩件事情,1、將當前處理器緩存行的數據寫回到系統內存。2、這個寫會內存的操作會使在其他CPU里緩存了該內存地址的數據無效。
 
正常情況下,處理器不直接和內存進行通信,而是先將系統內存的數據讀取到內部緩存(L1,L2或其他),但操作完不知道何時會寫到內存。如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發送一條LOCK前綴的指令,將這個變量所在的緩存行的數據寫回到內存。但是,就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題。所有,在多處理器下,為了保證各個處理器緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己的緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置為無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統內存中把數據都到處理器緩存里。
 
volatile兩個實現原則
1)在執行指令期間,聲言處理器的LOCK#信號。在多處理器環境中,LOCK#信號確保在聲言該信號期間,處理器可以獨占任何共享內存。在老的處理器中,LOCK#信號一般鎖總線,但是鎖總線的開銷比較大,現在的處理器,一般鎖緩存。
2)一個處理器的緩存回寫到內存內會導致其他處理器的緩存無效。
 
這里提到的總線鎖和緩存鎖,可以參考: https://blog.csdn.net/summermangozz/article/details/75098773
 
volatile所能保證的是可見性,不能保證Java程序的原子性。
還是以最常用的i++來說吧,包含3個步驟
1,從內存讀取i當前的值
2,加1
3,把修改后的值刷新到內存
對於普通變量來說多線程下1,2之間被中斷,其它線程修改了i的值,那原來已經在1,2之間被中斷的線程的i的值就已經無效了,所以多線程是不安全的。
另外對於普通變量來說,步驟1並不是每次都會從內存中讀取,步驟3也並不會每次都保證會立即刷新到內存。
所以這里有兩個問題,可見性和原子性,viloate只能保證可見性,即步驟1每次都重新讀,步驟3每次都立即刷新到主內存。但1,2之間仍然會被中斷,多個線程交叉修改,所以仍然不安全。
 
synchronized,Java中每個對象都可以作為鎖。3種形式:1、普通同步方法,鎖就是當前實例對象。2、靜態同步方法,鎖是當前類的class對象。3、同步方法塊,鎖是Synchronized括號里配置的對象。
 
JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步,但是實現細節不同,代碼塊同步是實用monitorenter和monitorexit指令實現的,而方法同步是實用另外一種方式實現。monitorenter插入在代碼塊的開始位置,而monitorexit是插入在代碼塊結束和異常處,保證每個monitorenter必須有對應的monitorexit配對,任何一個對象都有monitor關聯,當且一個monitor被持有后,就處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲取對象的鎖。
 
Java SE1.6為了減少獲取鎖和釋放鎖的性能消耗,引入了“偏向鎖”和“輕量級鎖”,總共四種狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。
 
上圖,主要是對象頭的詳細說明。
 
偏向鎖
主要針對只有一個線程訪問同步塊的場景(很多情況下不存在多線程競爭,總是由同一個鎖多次獲得),如果是這種情況直接在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖。Word里是否存儲着指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
偏向鎖的撤銷,等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。
缺點是如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。
 
輕量級鎖
競爭的線程不會阻塞,提高程序的響應速度,但是如果始終得不到鎖競爭的線程,使用自旋會消耗CPU,適用於追求響應時間,同步塊執行速度非常快。
 
重量級鎖
線程競爭不適用自旋,不會消耗CPU,但是會線程阻塞,響應時間緩慢。適用於追求吞吐量,同步塊執行速度較長。
 
上圖是synchronized原理圖,分享一下。 
 
轉載請注明出處。
作者:wuxiwei
出處: http://www.cnblogs.com/wxw16/p/8926535.html 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM