1. 程式人生 > >深入分析 Java 樂觀鎖

深入分析 Java 樂觀鎖

![](https://img2020.cnblogs.com/other/633265/202012/633265-20201209151950313-132404733.jpg) # 前言 激烈的鎖競爭,會造成`執行緒阻塞掛起`,導致`系統的上下文切換`,增加系統的效能開銷。那有沒有不阻塞執行緒,且保證執行緒安全的機制呢?——`樂觀鎖`。 # 樂觀鎖是什麼? 操作共享資源時,總是很樂觀,認為自己可以成功。在操作失敗時(資源被其他執行緒佔用),並不會掛起阻塞,而僅僅是返回,並且失敗的執行緒可以重試。 優點: - 不會死鎖 - 不會飢餓 - 不會因競爭造成系統開銷 # 樂觀鎖的實現 ## CAS 原子操作 CAS。在 `java.util.concurrent.atomic` 中的類都是基於 CAS 實現的。 ![](https://img2020.cnblogs.com/other/633265/202012/633265-20201209151950552-1762750455.png) 以 AtomicLong 為例,一段測試程式碼: ```java @Test public void testCAS() { AtomicLong atomicLong = new AtomicLong(); atomicLong.incrementAndGet(); } ``` java.util.concurrent.atomic.AtomicLong#incrementAndGet 的實現方法是: ```java public final long incrementAndGet() { return U.getAndAddLong(this, VALUE, 1L) + 1L; } ``` 其中 U 是一個 Unsafe 例項。 ```java private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); ``` 本文使用的原始碼是 JDK 11,其 getAndAddLong 原始碼為: ```java @HotSpotIntrinsicCandidate public final long getAndAddLong(Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!weakCompareAndSetLong(o, offset, v, v + delta)); return v; } ``` 可以看到裡面是一個 while 迴圈,如果不成功就一直迴圈,是一個樂觀鎖,堅信自己能成功,一直 CAS 直到成功。最終呼叫了 native 方法: ```java @HotSpotIntrinsicCandidate public final native boolean compareAndSetLong(Object o, long offset, long expected, long x); ``` ## 處理器實現原子操作 從上面可以看到,CAS 是呼叫處理器底層的指令來實現原子操作,那麼處理器底層是如何實現原子操作的呢? 處理器的處理速度>>處理器與實體記憶體的通訊速度,所以在處理器內部有 L1、L2 和 L3 的快取記憶體,可以加快讀取的速度。 ![](https://img2020.cnblogs.com/other/633265/202012/633265-20201209151950710-1894454107.jpg) `單核處理器`能夠儲存記憶體操作是原子性的,當一個執行緒讀取一個位元組,所以程序和執行緒看到的都是同一個快取裡的位元組。但是`多核處理器`裡,每個處理器都維護了一塊位元組的記憶體,每個核心都維護了一個位元組的快取,多執行緒併發會存在`快取不一致`的問題。 那處理器如何保證記憶體操作的原子性呢? - 匯流排鎖定:當處理器要操作共享變數時,會在總線上發出 Lock 訊號,其他處理器就不能操作這個共享變量了。 - 快取鎖定:某個處理器對快取中的共享變數操作後,就通知其他處理器重新讀取該共享資源。 # LongAdder vs AtomicLong 本文分析的 AtomicLong 原始碼,其實是在迴圈不斷嘗試 CAS 操作,如果長時間不成功,就會給 CPU 帶來很大開銷。JDK 1.8 中新增了原子類 `LongAdder`,能夠更好應用於高併發場景。 LongAdder 的原理就是降低操作共享變數的併發數,也就是將對單一共享變數的操作壓力分散到多個變數值上,將競爭的每個寫執行緒的 value 值分散到一個數組中,不同執行緒會命中到陣列的不同槽中,各個執行緒只對自己槽中的 value 值進行 CAS 操作,最後在讀取值的時候會將原子操作的共享變數與各個分散在陣列的 value 值相加,返回一個近似準確的數值。 LongAdder 內部由一個base變數和一個 cell[] 陣列組成。當只有一個寫執行緒,沒有競爭的情況下,LongAdder 會直接使用 base 變數作為原子操作變數,通過 CAS 操作修改變數;當有多個寫執行緒競爭的情況下,除了佔用 base 變數的一個寫執行緒之外,其它各個執行緒會將修改的變數寫入到自己的槽 cell[] 陣列中。 一個測試用例: ```java @Test public void testLongAdder() { LongAdder longAdder = new LongAdder(); longAdder.add(1); System.out.println(longAdder.longValue()); } ``` 先看裡面的 `longAdder.longValue()` 程式碼: ```java public long longValue() { return sum(); } ``` 最終是呼叫了 sum() 方法,是對裡面的 cells 陣列每項加起來求和。這個值在讀取的時候並不準,因為這期間可能有其他執行緒在併發修改 cells 中某個項的值: ```java public long sum() { Cell[] cs = cells; long sum = base; if (cs != null) { for (Cell c : cs) if (c != null) sum += c.value; } return sum; } ``` add() 方法原始碼: ```java public void add(long x) { Cell[] cs; long b, v; int m; Cell c; if ((cs = cells) != null || !casBase(b = base, b + x)) { boolean uncontended = true; if (cs == null || (m = cs.length - 1) < 0 || (c = cs[getProbe() & m]) == null || !(uncontended = c.cas(v = c.value, v + x))) longAccumulate(x, null, uncontended); } } ``` add 具體的程式碼本篇文章就不詳細敘述了~ # 公眾號 coding 筆記、點滴記錄,以後的文章也會同步到公眾號(Coding Insight)中,希望大家關注^_^ 程式碼和思維導圖在 [GitHub 專案](https://github.com/LjyYano/Thinking_in_Java_MindMapping)中,歡迎大家 star! ![](https://img2020.cnblogs.com/other/633265/202012/633265-20201209151950929-1505639043.jpg)