JDK併發包溫故知新系列(四)—— CAS原理與JDK8的優化

什麼是CAS

CAS-CompareAndSet,是JDK原子變量類AtomicInteger、AtomicLong、AtomicInteger、AtomicBoolean、AtomicReference等實現的基礎,例如對於一個共享變量int,就算是簡單的自增操做也不是原子性的,多線程同時自增,可能會致使變量的值比預期結果小。可是可使用AtomicInteger的incrementAndGet() 方法操做變量,這樣結果和預期值同樣。跟傳統的加鎖不一樣,getAndDecrement()方法並無給代碼加鎖。代碼相似於:算法

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

底層經過sun.misc.Unsafe的本地方法compareAndSwapInt實現,這個方法是原子的。多線程

與synchronized的對比

  • 樂觀鎖與悲觀鎖的區別
  • 性能對比

synchronized是阻塞的,CAS更新是非阻塞的,只是會重試,不會有線程上下文切換開銷,對於大部分比較簡單的操做,不管是在低併發仍是高併發狀況下,這種樂觀非阻塞方式的性能都要遠高於悲觀阻塞式方式。併發

應用場景

  • 用來實現樂觀非阻塞算法,確保當前線程方法體內使用的共享變量不被其餘線程改變,CAS普遍運用在非阻塞容器中。
  • 用來實現悲觀阻塞式算法,其用在了顯式鎖的原理實現,如可重入計數中,調用lock()方法時將經過CAS方法將其設爲1,調用unlock則設爲遞減1。若是同時多個線程調用Lock方法那麼必然會致使原子修改不成功,保證了鎖的機制,排他性。

可能存在的問題

  • ABA問題,普通的CAS操做並非原子的,由於有可能另外一個線程改了值可是又改回了值,那麼樂觀鎖的方式是不能保證原子性的,若業務須要規避這種狀況那麼可使用AtomicStampedReference的compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)方法,只有值和時間戳都相等的時候才進行原子更新,每次更新都把當前時間修改進原子變量。

JDK8的優化

JAVA8新增了LongAdder、DoubleAdder對原子變量進行進一步優化,主要是利用了分段CAS的機制,若是不用LongAdder,用AtomicLong的話,在高併發狀況下,會產生一直自旋,致使效率不高。他將一個數分紅若干個數,CompareAndSet方法的參數只是比較的這若干個數中的一個數,從而下降了自旋的機率,提升了效率。高併發

相關文章
相關標籤/搜索