作者: 一字馬胡
轉載標志 【2017-11-03】
更新日志
日期 | 更新內容 | 備注 |
---|---|---|
2017-11-03 | 添加轉載標志 | 持續更新 |
在進行java并發編程時,volatile和synchronized的使用是相當廣泛的,為了安全的進行并發編程,學習和使用volatile和synchronized也是相當有必要的。
一、volatile
在java語言中,使用多個線程來訪問共享變量是一種常見的并發場景,這就使得多個線程可能同時修改共享變量,那么限制多個線程排他的訪問共享變量就變得非常有必要了。java提供了多種方式來達到限制多線程排他的訪問共享變量的方法,但是代價也是不同的。使用volatile是一種非常輕量級的方式。如果一個變量被聲明為volatile,那么jvm將保證所有線程看到的都是同一個共享變量。為什么不同線程看到的變量可能不一樣呢?因為為了提高處理速度,cpu不直接和內存交互,而是首先將內存讀取到內部緩存(L1,L2等)中,然后cpu就直接和內部緩存通信來提高處理速度。對于具有多個cpu的機器來說,不同的線程可能都在訪問某個共享變量,而不同的線程運行在不同的cpu里面,所以同一個變量可能被緩存在多個cpu內部緩存里面,如果沒有volatile來保證共享變量對于多線程是一致的話,就可能發生多個線程訪問到的共享變量具備不同的值,因為我們不知道cpu會在什么時候將緩存的值寫回到內存中去。
在實現上,如果對被volatile修飾的共享變量執行寫操作的話,JVM就會向cpu發送一條Lock前綴的指令,cpu將會這個變量所在的緩存行(緩存中可以分配的最小緩存單位)寫回到內存中去。但是在多處理器的情況下,將某個cpu上的緩存行寫回到系統內存之后,其他cpu上該變量的緩存還是舊的,這樣再進行后面的操作的時候就會出現問題,所以為了使得所有線程看到的內容都是一致的,就需要實現緩存一致性協議,cpu將會通過監控總線上傳遞過來的數據來判斷自己的緩存是否過期,如果過期,就需要使得緩存失效,如果cpu再來訪問該緩存的時候,就會發現緩存失效了,這時候就會重新從內存加載緩存。
總結一下,volatile的實現原則有兩條:
1、JVM的Lock前綴的指令將使得cpu緩存寫回到系統內存中去
2、為了保證緩存一致性原則,在多cpu的情景下,一個cpu的緩存回寫內存會導致其他的
cpu上的緩存都失效,再次訪問會重新從系統內存加載新的緩存內容。
二、synchronized
相對于volatile,synchronized就顯得比較重量級了。
首先,我們應該知道,在java中,所有的對象都可以作為鎖。可以分為下面三種情況:
1、普通方法同步,鎖是當前對象
2、靜態方法同步,鎖是當前類的Class對象
3、普通塊同步,鎖是synchronize里面配置的對象
當一個線程試圖訪問同步代碼時,必須要先獲得鎖,退出或者拋出異常時必須要釋放鎖。
JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步,可以使用monitorenter和monitorexit指令實現。monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit指令則插入到方法結束和異常處,JVM保證每個monitorenter都有一個monitorexit閾值相對應。線程執行到monitorenter的時候,會嘗試獲得對象所對應的monitor的鎖,然后才能獲得訪問權限,synchronize使用的鎖保存在Java對象頭中。