阿里面試必問之CAS與LongAdder

簡介

阿里面試的時(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的原因。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容