一、前言
假設現在有多個線程對一個變量不停累加,如果直接對這個變量做 ++ 操作,是有問題的。
多線程對一個data變量時空行修改,是線程不安全的,會導致data值的變化不
遵循預期的值來改變。
二、初初步解決:synchronized
對方法上加synchronized關鍵字,這樣初步的解決“太重了”。
更高效解決方案:Atomic原子類及其底層原理
java併發包下提供了一系列Atomic原子類,比如說AInterger。
他可保證多線程併發安全的情況下,高性能的併發更新一個數值。
三、更高效的方案:Atomic原子類
1、AtomicInteger
private AutomicInteger data = new AutomicInteger(0);
//多個線程併發執行:data.incrementAndGet();
AutomicInteger 累加器,是通過無鎖化CAS(比較並交換)機制保證多線程修改一個數值的安全性。
2、incrementAndGet累加方法
步驟1:每次在修改data變量值時,會先獲取這個值
步驟2:接着執行一個cas操作來修改這值,如果修改成功返回
步驟3:修改失敗,那麼重複步驟1和步驟2,直到成功爲止
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
多個線程併發執行getAndAddInt方法,將data值加1後,再返回累加後最新的值
但這個cas有沒有問題呢?如果有大量線程同時併發修改一個AtomicInterger,可能有很多線程
會不停的自旋,於是java8推出了一個新的類,LongAdder
四、java 8對CAS機制的優化LongAdder
LongAdder:嘗試使用分段CAS以及自動分段遷移的方式來大幅提升多線程高併發執行CAS操作的性能!
1、LongAdder底層原理
transient volatile long base;
transient volatile Cell[] cells;
首先 LongAdder 維護了一個 base 值,這個值和 AtomicLong 中的 value 的作用一樣;除此之外,它還維護了一個 Cell[] cells 數組,初始值爲 null,只有在對 base 執行 CAS 更新失敗時(說明競爭激烈)纔會用到 cells 這個數組
Cell 類很簡單,裏面就放了一個 value 和一個支持 CAS 更新 value 的 cas 方法。它長度是 2 的 n 次冪,也是通過 h & (length - 1) 來獲取數組的下標。
當 n 個線程同時執行 add 方法時,
- 對於 AtomicLong 來說只會有一個線程會執行成功,剩下的都會失敗進入自旋,最終看起來就像是串行的在執行。
- 而對於 LongAdder,它會根據線程的 probe 值( Thread 類中的 threadLocalRandomProbe
字段)求得一個 cells 的數組下標,獲取到 cell 值。n 個線程被分散到不同的 cell 中,在執行 CAS失敗的概率就小得多了。
2、add()方法源碼流程圖:
3、sum()方法:
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
sum 方法是返回當前總和,但這個返回值並不是一個原子快照
假如在 sum 的過程中,沒有線程調用 add 方法,這種情況下返回的值是準確的;但在這個過程中,cell[0] 已經被加過了,這時恰好有一個線程調用了 add 方法,對 cell[0] 作了更新,但這個時候 sum 方法已經統計不到了,所以這種值不會被加進來。
4、LongAdder總結
所以它的特點總結起來就是:
- 在高併發下有更好的性能表現;
- 但高併發下不能保證 sum 返回值的準確性;