
前言
激烈的鎖競爭,會造成執行緒阻塞掛起,導致系統的背景關系切換,增加系統的性能開銷,那有沒有不阻塞執行緒,且保證執行緒安全的機制呢?——樂觀鎖,
樂觀鎖是什么?
操作共享資源時,總是很樂觀,認為自己可以成功,在操作失敗時(資源被其他執行緒占用),并不會掛起阻塞,而僅僅是回傳,并且失敗的執行緒可以重試,
優點:
- 不會死鎖
- 不會饑餓
- 不會因競爭造成系統開銷
樂觀鎖的實作
CAS 原子操作
CAS,在 java.util.concurrent.atomic 中的類都是基于 CAS 實作的,

以 AtomicLong 為例,一段測驗代碼:
@Test
public void testCAS() {
AtomicLong atomicLong = new AtomicLong();
atomicLong.incrementAndGet();
}
java.util.concurrent.atomic.AtomicLong#incrementAndGet 的實作方法是:
public final long incrementAndGet() {
return U.getAndAddLong(this, VALUE, 1L) + 1L;
}
其中 U 是一個 Unsafe 實體,
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
本文使用的原始碼是 JDK 11,其 getAndAddLong 原始碼為:
@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 方法:
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetLong(Object o, long offset,
long expected,
long x);
處理器實作原子操作
從上面可以看到,CAS 是呼叫處理器底層的指令來實作原子操作,那么處理器底層是如何實作原子操作的呢?
處理器的處理速度>>處理器與物理記憶體的通信速度,所以在處理器內部有 L1、L2 和 L3 的高速快取,可以加快讀取的速度,

單核處理器能夠保存記憶體操作是原子性的,當一個執行緒讀取一個位元組,所以行程和執行緒看到的都是同一個快取里的位元組,但是多核處理器里,每個處理器都維護了一塊位元組的記憶體,每個內核都維護了一個位元組的快取,多執行緒并發會存在快取不一致的問題,
那處理器如何保證記憶體操作的原子性呢?
- 總線鎖定:當處理器要操作共享變數時,會在總線上發出 Lock 信號,其他處理器就不能操作這個共享變數了,
- 快取鎖定:某個處理器對快取中的共享變數操作后,就通知其他處理器重新讀取該共享資源,
LongAdder vs AtomicLong
本文分析的 AtomicLong 原始碼,其實是在回圈不斷嘗試 CAS 操作,如果長時間不成功,就會給 CPU 帶來很大開銷,JDK 1.8 中新增了原子類 LongAdder,能夠更好應用于高并發場景,
LongAdder 的原理就是降低操作共享變數的并發數,也就是將對單一共享變數的操作壓力分散到多個變數值上,將競爭的每個寫執行緒的 value 值分散到一個陣列中,不同執行緒會命中到陣列的不同槽中,各個執行緒只對自己槽中的 value 值進行 CAS 操作,最后在讀取值的時候會將原子操作的共享變數與各個分散在陣列的 value 值相加,回傳一個近似準確的數值,
LongAdder 內部由一個base變數和一個 cell[] 陣列組成,當只有一個寫執行緒,沒有競爭的情況下,LongAdder 會直接使用 base 變數作為原子操作變數,通過 CAS 操作修改變數;當有多個寫執行緒競爭的情況下,除了占用 base 變數的一個寫執行緒之外,其它各個執行緒會將修改的變數寫入到自己的槽 cell[] 陣列中,
一個測驗用例:
@Test
public void testLongAdder() {
LongAdder longAdder = new LongAdder();
longAdder.add(1);
System.out.println(longAdder.longValue());
}
先看里面的 longAdder.longValue() 代碼:
public long longValue() {
return sum();
}
最終是呼叫了 sum() 方法,是對里面的 cells 陣列每項加起來求和,這個值在讀取的時候并不準,因為這期間可能有其他執行緒在并發修改 cells 中某個項的值:
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() 方法原始碼:
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 專案中,歡迎大家 star!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/232262.html
標籤:Java
