簡介
阿里面試的時(shí)候經(jīng)常會(huì)問到高并發(fā),解決并發(fā)的方案就是cas,也是AtomicLong這些原子類,那么如果問你除了Atomic這些原子類之外的?解法呢?
cas
java.util.concurrent.atomic 包下的原子操作類都是基于 CAS 實(shí)現(xiàn)的,CAS的全稱是 Compare-and-Swap,也就是比較并交換。java.util.concurrent 包全完建立在CAS之上,沒有CAS也就沒有此包,可見CAS的重要性。
AtomicLong 類變量的定義:
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile long value;
關(guān)鍵屬性:
1、Unsafe是CAS的核心類
2、valueOffset表示的是變量值在內(nèi)存中的偏移地址,因?yàn)?Unsafe 就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)的原值的。
3、value 是用volatile修飾的。
Unsafe類,cmpxchg方法
簡單科普一下unsafe和cmpxchg:
Unsafe類是CAS的核心,Java 無法直接訪問底層操作系統(tǒng),而是通過本地方法來訪問。不過盡管如此,JVM 還是開了一個(gè)后門,JDK 中有一個(gè)類 Unsafe,它提供了硬件級(jí)別的原子操作。
Unsafe位置在:sun.misc.Unsafe
Unsafe是通過JNI調(diào)用到unsafe.cpp中,而unsafe.cpp中的實(shí)現(xiàn)本質(zhì)都是調(diào)用CPU的cmpxchg指令,也就是說cmpxchg是cpu級(jí)別的cas。
unsafe.cpp位置:/OpenJDK/hotspot/src/share/vm/prims/unsafe.cpp
cmpxchg位置:/OpenJDK/hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.hpp
簡單了解下就可以了。
Atomic的實(shí)現(xiàn)
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
getAndIncrement() 會(huì)無限重試,直到CAS執(zhí)行成功后退出循環(huán),由此可見在高并發(fā)下發(fā)生CAS失敗的幾率也就越大,會(huì)導(dǎo)致循環(huán)次數(shù)過多從而帶來額外性能問題。
cas缺點(diǎn)
1.aba問題:
CAS操作容易導(dǎo)致ABA問題,也就是在做a++之間,a可能被多個(gè)線程修改過了,只不過回到了最初的值,這時(shí)CAS會(huì)認(rèn)為a的值沒有變。就是一個(gè)值從A變成了B又變成了A,使用CAS操作不能發(fā)現(xiàn)這個(gè)值發(fā)生變化了。
2.性能問題
我們使用時(shí)大部分時(shí)間使用的是 while true 方式對(duì)數(shù)據(jù)的修改,直到成功為止。優(yōu)勢就是響應(yīng)極快,但當(dāng)線程數(shù)不停增加時(shí),性能下降明顯,因?yàn)槊總€(gè)線程都需要執(zhí)行,占用CPU時(shí)間。
LongAdder
LongAdder并沒有提供AtomicLong的getAnIncrement()或者IncrementAndGet()這樣的原子操作,增加操作是increment()獲取當(dāng)前值是longValue(),增加和獲取的操作是分離的,沒有原子性,這是其跟AtomicLong的區(qū)別。
longValue()
/**
* Equivalent to {@link #sum}.
*
* @return the sum
*/
public long longValue() {
return 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;
}
從longValue()方法可以看出,LongAdder的值是通過cell數(shù)組的value累加以及base的加和來算出來的,這么設(shè)計(jì)的意義何在?
Cell
cell數(shù)組是longAdder中的成員變量,cell也是Striped64的內(nèi)部類。
/**
* Table of cells. When non-null, size is a power of 2.
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
longAdder的increment和decrement都是通過add()方法來實(shí)現(xiàn)的:
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
*/
public void decrement() {
add(-1L);
}
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
add方法先判斷一下cells是否為空,當(dāng)方法首次執(zhí)行的時(shí)候肯定為空,因此會(huì)進(jìn)入casBase()方法
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
casBase方法就是簡單的CAS操作,當(dāng)casBase執(zhí)行成功的時(shí)候就不會(huì)進(jìn)入if判斷內(nèi)部,直接結(jié)束方法,那什么時(shí)候casBase執(zhí)行失敗?就是高并發(fā)的情況下,AtomicLong的處理方式是死循環(huán)嘗試更新,直到成功才返回,而LongAdder則是進(jìn)入這個(gè)if里的邏輯。這里就可以看出當(dāng)并發(fā)不大的時(shí)候只對(duì)base進(jìn)行更新,獲取值得時(shí)候只從base獲取即可,這個(gè)時(shí)候其實(shí)和AtomicLong的原理幾乎一模一樣,區(qū)別就在于if里的的分支,繼續(xù)往下看。
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
if里的四個(gè)條件是遞進(jìn)關(guān)系:
as == null ,(m = as.length - 1) < 0 ,這兩個(gè)是判斷cell數(shù)組是否為空;
a = as[getProbe() & m]) == null :這個(gè)條件是取cells數(shù)組中的任意一個(gè)元素a判斷是否為空;
uncontended = a.cas(v = a.value, v + x) :這個(gè)條件是第三個(gè)條件進(jìn)行之后,取cells數(shù)組中的任意一個(gè)元素a然后進(jìn)行cas操作。
從這里可以看出結(jié)論,當(dāng)cas競爭激烈到一定程度時(shí),會(huì)對(duì)cells數(shù)組中某個(gè)隨機(jī)元素進(jìn)行更新。
結(jié)論
因?yàn)橹萍sAtomicLong高效的原因是高并發(fā),高并發(fā)意味著CAS的失敗幾率更高, 重試次數(shù)更多,越多線程重試,CAS失敗幾率又越高,變成惡性循環(huán),AtomicLong效率降低。 LongAdder給了一個(gè)非常巧妙的解決方案: 減少并發(fā),將單一value的更新壓力分擔(dān)到多個(gè)value中去,用空間換時(shí)間,分段更新。這樣,線程數(shù)再多也會(huì)分擔(dān)到不同的value上去更新,這樣就能解決AtomicLong中的 惡性循環(huán)。 cells就是這個(gè) “段” ,cell中的value 就是存放更新值的,當(dāng)我需要總數(shù)時(shí),只需要把cells 中的value都累加一下就可以了。這也是為什么longValue()方法需要sum一下cell數(shù)組與base的原因。