Java鎖機制
(一)執行緒安全問題
執行緒安全是多執行緒領域的問題,執行緒安全可以簡單理解為一個方法或者一個例項可以在多執行緒環境中使用而不會出現問題。
在 Java 多執行緒程式設計當中,提供了多種實現 Java 執行緒安全的方式:
1、最簡單的方式,使用Synchronization
關鍵字
2、使用java.util.concurrent.atomic
包中的原子類,例如AtomicInteger
3、
使用java.util.concurrent.locks
包中的鎖
4、使用執行緒安全的集合ConcurrentHashMap
5、
使用volatile
關鍵字,保證變數可見性(直接從記憶體讀,而不是從執行緒cache
(二)volatile 實現原理
(1)在 JVM 底層 volatile 是採用“記憶體屏障”來實現的
(2)快取一致性協議(MESI協議)它確保每個快取中使用的共享變數的副本是一致的。其核心思想如下:當某個 CPU 在寫資料時,如果發現操作的變數是共享變數,則會通知其他 CPU 告知該變數的快取行是無效的,因此其他 CPU 在讀取該變數時,發現其無效會重新從主存中載入資料
(三)synchronize 實現原理
同步程式碼塊是使用monitorenter
和monitorexit
指令實現的,同步方法(在這看不出來需要看 JVM 底層實現)依靠的是方法修飾符上的ACC_SYNCHRONIZED
(四)synchronized 與 lock 的區別
1、synchronized 和 lock 的用法區別
synchronized(隱式鎖):在需要同步的物件中加入此控制,synchronized
可以加在方法上,也可以加在特定程式碼塊中,括號中表示需要鎖的物件。
lock(顯示鎖):需要顯示指定起始位置和終止位置。一般使用ReentrantLock
類做為鎖,多個執行緒中必須要使用一個ReentrantLock
類做為物件才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()
和unlock()
顯示指出。所以一般會在finally
塊中寫unlock()
以防死鎖。
2、synchronized 和 lock 效能區別synchronized
lock
是 Java 寫的控制鎖的程式碼。在JDK 1.5中,synchronize
是效能低效的。因為這是一個重量級操作,需要呼叫操作介面,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用 Java 提供的Lock
物件,效能更高一些。但是到了JDK 1.6,發生了變化。synchronize
在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK 1.6上synchronize
的效能並不比Lock
差。
3、synchronized 和 lock 機制區別
synchronized
原始採用的是 CPU 悲觀鎖機制,即執行緒獲得的是獨佔鎖。獨佔鎖意味著其 他執行緒只能依靠阻塞來等待執行緒釋放鎖。
Lock
用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。樂觀鎖實現的機制就是 CAS 操作(Compare and Swap)。
(五)CAS 樂觀鎖
CAS 是項樂觀鎖技術,當多個執行緒嘗試使用 CAS 同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”這其實和樂觀鎖的衝突檢查 + 資料更新的原理是一樣的。
(六)ABA 問題
CAS 會導致“ABA問題”。
CAS 演算法實現一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較並替換,那麼在這個時間差類會導致資料的變化。
比如說一個執行緒 one 從記憶體位置 V 中取出 A,這時候另一個執行緒 two 也從記憶體中取出 A,並且 two 進行了一些操作變成了 B,然後 two 又將 V 位置的資料變成 A,這時候執行緒 one 進行 CAS 操作發現記憶體中仍然是 A,然後 one 操作成功。儘管執行緒 one 的 CAS 操作成功,但是不代表這個過程就是沒有問題的。
部分樂觀鎖的實現是通過版本號(version)的方式來解決 ABA 問題,樂觀鎖每次在執行資料的修改操作時,都會帶上一個版本號,一旦版本號和資料的版本號一致就可以執行修改操作並對版本號執行 +1 操作,否則就執行失敗。因為每次操作的版本號都會隨之增加,所以不會出現 ABA 問題,因為版本號只會增加不會減少。
(七)樂觀鎖的業務場景及實現方式
樂觀鎖(Optimistic Lock):
1、每次獲取資料的時候,都不會擔心資料被修改,所以每次獲取資料的時候都不會進行加鎖,但是在更新資料的時候需要判斷該資料是否被別人修改過。如果資料被其他執行緒修改,則不進行資料更新,如果資料沒有被其他執行緒修改,則進行資料更新。由於資料沒有進行加鎖,期間該資料可以被其他執行緒進行讀寫操作。
2、比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,資料發生衝突的可能性就會增大,為了保證資料的一致性,應用層需要不斷的重新獲取資料,這樣會增加大量的查詢操作,降低了系統的吞吐量。
轉自:有夢想的鹹魚