JDK原子類源碼分析

JDK中的java.util.concurrent.atomic包提供了一系列支持無(wú)鎖線程安全修改操作的基礎(chǔ)變量。這些原子類是對(duì)volatile機(jī)制的擴(kuò)展,并且提供了一系列CAS原子操作方法如:

 boolean compareAndSet(expectedValue, updateValue);

這些CAS方法使得我們能夠利用機(jī)器底層的原子指令,雖然在某些機(jī)器平臺(tái)上,這些指令可能會(huì)牽涉到一些內(nèi)部的加鎖,這使得這些方法不是嚴(yán)格遵守非阻塞-即線程可能在執(zhí)行CAS指令前阻塞。

AtomicXxx原子類

AtomicLong/AtomicInteger/AtomicBoolean/AtomicReference這幾個(gè)原子類提供了對(duì)這些原子類相關(guān)的基礎(chǔ)類型的get/set操作,同時(shí)也提供了一些工具方法。如AtomicLong/AtomicInteger其中一種應(yīng)用場(chǎng)景就是序列號(hào)生成:

 class Sequencer {
   private final AtomicLong sequenceNumber
     = new AtomicLong(0);
   public long next() {
     return sequenceNumber.getAndIncrement();
 }}

同時(shí)我們還可以根據(jù)這些原子類提供的工具方法,實(shí)現(xiàn)我們自己的工具方法,舉個(gè)例子:

    private long transform(long aa){
        return aa+2;
    }
    public long casTransform(AtomicLong var){//原子transform
        long pre,next;
        do{
            pre = var.get();
            next = transform(pre);
        }while (!var.compareAndSet(pre, next));
        return pre;
    }

這種用法相當(dāng)于我們通過(guò)AtomicXxxx類代理使用了CPU層面的CAS操作。
下面我們一起看一下這一類原子類的代碼實(shí)現(xiàn)(以AtomicLong為例),會(huì)發(fā)現(xiàn)十分簡(jiǎn)潔,大部分工作透過(guò)UNSAFE實(shí)例實(shí)現(xiàn)。

  • get()/set()
private volatile long value;
public final long get() {
        return value;
    }
public final void set(long newValue) {
        value = newValue;
    }

由于value是volatile修飾的,故value在多線程間保持可見(jiàn)性,在多線程情況下可以無(wú)鎖訪問(wèn)/修改。

  • compareAndSet
public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

原子的設(shè)置value為update,實(shí)際的操作是由UNSAFE實(shí)例代理的。

public final native boolean compareAndSwapLong((Object) o, long offset, long expected,
 long x);

在Unsafe中,compareAndSwapLong是一個(gè)native方法,也就是該方法的具體實(shí)現(xiàn)是由JVM的本地方法實(shí)現(xiàn)的。我們跟蹤了openjdk中HotSpotVM的源碼,其實(shí)也很簡(jiǎn)單:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
  UnsafeWrapper("Unsafe_CompareAndSwapLong");
  Handle p (THREAD, JNIHandles::resolve(obj));
  jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
  if (VPC_Version::supports_cx8())
    <b>return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;</b>
  else {
    jboolean success = false;
    ObjectLocker ol(p, THREAD);
    if (*addr == e) { *addr = x; success = true; }
    return success;
  }
UNSAFE_END
//linux_x86
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_PCP();
  __asm__ volatile (LOCK_IF_PCP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

最終執(zhí)行的是匯編指令,CPU執(zhí)行cmpxchgl指令對(duì)expextVal和updateVal進(jìn)行比較交換操作。同時(shí),虛擬機(jī)也根據(jù)不同的系統(tǒng)平臺(tái)改用對(duì)應(yīng)的操作指令:

//linux_sparc
inline jlong    Atomic::cmpxchg    (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value) {
#ifdef _LP64
  jlong rv;
  __asm__ volatile(
   " casx   [%2], %3, %0"  //casx指令
    : "=r" (rv)
    : "0" (exchange_value), "r" (dest), "r" (compare_value)
    : "memory");
  return rv;
#else
  assert(VM_Version::v9_instructions_work(), "cas only supported on v9");
  volatile jlong_accessor evl, cvl, rv;
  evl.long_value = exchange_value;
  cvl.long_value = compare_value;

  __asm__ volatile(
    " sllx   %2, 32, %2\n\t"
    " srl    %3, 0,  %3\n\t"
    " or     %2, %3, %2\n\t"
    " sllx   %5, 32, %5\n\t"
    " srl    %6, 0,  %6\n\t"
    " or     %5, %6, %5\n\t"
    " casx   [%4], %5, %2\n\t"
    " srl    %2, 0, %1\n\t"
    " srlx   %2, 32, %0\n\t"
    : "=r" (rv.words[0]), "=r" (rv.words[1])
    : "r"  (evl.words[0]), "r" (evl.words[1]), "r" (dest), "r" (cvl.words[0]), "r" (cvl.words[1])
    : "memory");

  return rv.long_value;
#endif
}
  • 循環(huán)CAS操作函數(shù)
    getAndAdd/getAndSet/getAndIncrement/getAndDecrement/incrementAndGet/decrementAndGet/addAndGet這一系列的方法的實(shí)現(xiàn)原理都是一樣的,所以放在一起說(shuō)。它們的套路都是先得到value的值,然后自己做操作(加/減),然后CAS設(shè)置value為目標(biāo)值,失敗就循環(huán)的執(zhí)行上述步驟,成功就可以返回(這些方法的區(qū)別就是,getAndXxxx系列返回的是value的原始值,而xxxAndGet系列返回的是修改成功后的值)。這個(gè)實(shí)現(xiàn)的核心就在于循環(huán)的CAS操作知道CAS成功。以getAndIncrement為例:
    <pre>
    public final long getAndIncrement() {
    while (true) {
    long current = get();
    long next = current + 1;
    if (compareAndSet(current, next))
    return current;
    }
    }
    </pre>

AtomicXxxxArray

原子更新數(shù)組,這一系列的類可以讓我們?cè)拥母聰?shù)組的元素。

AtomicXxxxFieldUpdater

如果我們要原子更新一個(gè)類的某個(gè)字段,就可以使用AtomicXxxxFieldUpdater這個(gè)系列的原子類。下面以AtomicLongFieldUpdater為例說(shuō)明。AtomicLongFieldUpdater<T>可以讓你原子的修改類T的long類型成員,AtomicLongFieldUpdater是一個(gè)抽象類,內(nèi)部實(shí)現(xiàn)了兩個(gè)靜態(tài)私有類CASUpdater<T>與LockedUpdater<T>,所以創(chuàng)建AtomicLongFieldUpdater實(shí)例的時(shí)候,需要調(diào)用靜態(tài)方法newUpdater。AtomicLongFieldUpdater的使用實(shí)例如下:

public class AtomicTest {
    static class Person {
        private long id;
        private String name;
        private short sex;
    }
    public static void main(String[] args) {
        AtomicLongFieldUpdater<Person> updater = AtomicLongFieldUpdater.newUpdater(Person.class, "id");
        Person person = new Person();
        updater.addAndGet(person, 123353647L);
    }
}
  • getAndXxx/incrementAndGet/decrementAndGet
    該系列方法的實(shí)現(xiàn)與AtomicLong這些原子類的實(shí)現(xiàn)是類似的,也即通過(guò)循環(huán)CAS來(lái)設(shè)置目標(biāo)值。而具體調(diào)用CASUpdater<T>還是LockedUpdater<T>的CAS函數(shù)則是根據(jù)JVM是否支持long類型的無(wú)鎖cas操作而異。在new一個(gè)AtomicLongFieldUpdater時(shí),如果運(yùn)行該程序的JVM支持long類型的無(wú)鎖cas操作,就會(huì)new一個(gè)CASUpdater,反之會(huì)new一個(gè)LockedUpdater。
public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) {
        Class<?> caller = Reflection.getCallerClass();
        if (AtomicLong.VM_SUPPORTS_LONG_CAS)
            return new CASUpdater<U>(tclass, fieldName, caller);
        else
            return new LockedUpdater<U>(tclass, fieldName, caller);
    }
/**
     * Records whether the underlying JVM supports lockless
     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong
     * method works in either case, some constructions should be
     * handled at Java level to avoid locking user-visible locks.
     */
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();

    /**
     * Returns whether underlying JVM supports lockless CompareAndSet
     * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
     */
    private static native boolean VMSupportsCS8();//該native方法只執(zhí)行一次

這兩個(gè)類的compareAndSet函數(shù)實(shí)現(xiàn)如下:

//CASUpdater
public boolean compareAndSet(T obj, long expect, long update) {
            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
            return unsafe.compareAndSwapLong(obj, offset, expect, update);
        }
//LockedUpdater
public boolean compareAndSet(T obj, long expect, long update) {
            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
            synchronized (this) {
                long v = unsafe.getLong(obj, offset);
                if (v != expect)
                    return false;
                unsafe.putLong(obj, offset, update);
                return true;
            }
        }

我們可以非常清楚的看到它們的異同。由于LockedUpdater.compareAndSet是在JVM不支持long類型的無(wú)鎖cas操作下調(diào)用的,所以該cas操作需要顯示加鎖來(lái)實(shí)現(xiàn)CAS操作,因此性能相比CASUpdater類會(huì)下降。對(duì)于其他get/set操作,也是類似情況。

AtomicReference

AtomicReference用來(lái)原子的更新一個(gè)對(duì)象引用。
AtomicReferenceArray用來(lái)原子的更新一個(gè)對(duì)象引用數(shù)組中的一個(gè)元素。
AtomicStampedReference維護(hù)了一個(gè)對(duì)象引用以及該引用對(duì)應(yīng)的一個(gè)版本號(hào)。在AtomicStampedReference內(nèi)部有一個(gè)私有的Pair數(shù)據(jù)結(jié)構(gòu)保存這兩個(gè)值。同時(shí),AtomicStampedReference有一個(gè)volatile的Pair類型成員變量。

private volatile Pair<V> pair;
private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

AtomicStampedReference在設(shè)置它的引用時(shí),必須同時(shí)設(shè)置版本號(hào)。

public void set(V newReference, int newStamp) {
        Pair<V> current = pair;
        if (newReference != current.reference || newStamp != current.stamp)
            this.pair = Pair.of(newReference, newStamp);//pair為volitile
    }

AtomicStampedReference提供的cas接口與其他Atomic類都不太一樣,該方法在reference與stamp都與當(dāng)前的兩個(gè)值一樣的情況下才能成功設(shè)置至兩個(gè)值為新的值。AtomicStampedReference這種特殊的cas可以解決CAS實(shí)現(xiàn)原子操作的ABA問(wèn)題,我們?cè)谧詈髸?huì)解釋。

//最后的casPair通過(guò)UNSAFE原子的修改兩個(gè)Pair變量
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

AtomicStampedReference可以視作是帶版本號(hào)的AtomicReference,每次更新AtomicStampedReference都需要設(shè)置新的版本號(hào)。而AtomicMarkableReference可以視作為帶布爾標(biāo)記的AtomicReference。AtomicMarkableReference與AtomicStampedReference的操作方法實(shí)現(xiàn)基本一樣。

CAS實(shí)現(xiàn)原子操作的問(wèn)題

  • ABA問(wèn)題
    CAS操作的原理就是在對(duì)目標(biāo)變量進(jìn)行操作時(shí),先檢查值是否發(fā)生了變化,如果沒(méi)有變化則更新,否則返回失敗并重試。但可能會(huì)存在這樣一種問(wèn)題,如果一個(gè)值原來(lái)是A,變成了B,又變成了A,則使用CAS進(jìn)行檢查時(shí)就會(huì)認(rèn)為它的值沒(méi)有變化,而實(shí)際卻是變化了。這個(gè)問(wèn)題的解決方案就是我們給每次變化加一個(gè)版本號(hào),只要每次變量被修改,就對(duì)版本號(hào)加一,版本號(hào)是線性增長(zhǎng)的,不會(huì)出現(xiàn)A->B->A這種情況,每次CAS比較的時(shí)候同時(shí)比較這兩個(gè)的值,如果這兩個(gè)值沒(méi)有同時(shí)相等,就返回修改失敗,重試直到成功。JDK里為我們提供了解決方案,就是AtomicStampedReference,上一節(jié)里對(duì)其原理有分析。
  • 只能保證一個(gè)共享變量是原子操作
    當(dāng)一個(gè)共享變量出現(xiàn)并發(fā)修改,我們可以使用循環(huán)CAS的方式保證原子操作,但是對(duì)于多個(gè)共享變量,我們就無(wú)法使用循環(huán)CAS操作來(lái)保證對(duì)多個(gè)共享變量的原子操作。這時(shí)候除了可以使用鎖以外,我們還可以把這兩個(gè)變量放到一個(gè)類中,通過(guò)AtomicReference引用這個(gè)類的實(shí)例,當(dāng)要對(duì)這兩個(gè)變量進(jìn)行原子修改時(shí),調(diào)用其compareAndSet方法修改指向新的變量的引用即可。
參考
  • 《Java并發(fā)編程的藝術(shù)》
  • JDK1.7源碼
最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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