java併發(十六)——atomic包

atomic包中的類都是通過unsafe的CAS操作實現的線程安全的工具類。CAS指的是compareAndSet(),它是又CPU指令實現的原子操作。

AtomicBoolean

AtomicBoolean是一個讀和寫都是原子性的boolean類型的變量。
下面是使用compareAndSet()的例子:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);

boolean expectedValue = true;
boolean newValue      = false;

boolean wasNewValueSet = atomicBoolean.compareAndSet(
    expectedValue, newValue);

AtomicInteger、AtomicLong

這兩個類是對基本數據類型的併發安全操作的類,能夠實現線程安全的自增操作,同時它的實現也是通過CAS來完成。

LongAdder、DoubleAdder

LongAdder是jdk8新增的用於併發環境的計數器,目的是爲了在高併發情況下,代替AtomicLong/AtomicInt,成爲一個用於高併發情況下的高效的通用計數器。高併發下計數,一般最先想到的應該是AtomicLong/AtomicInt,AtmoicXXX使用硬件級別的指令 CAS 來更新計數器的值,這樣可以避免加鎖,機器直接支持的指令,效率也很高。

但是AtomicXXX中的 CAS 操作在出現線程競爭時,失敗的線程會白白地循環一次,在併發很大的情況下,因爲每次CAS都只有一個線程能成功,競爭失敗的線程會非常多。失敗次數越多,循環次數就越多,很多線程的CAS操作越來越接近自旋鎖(spin lock)。計數操作本來是一個很簡單的操作,實際需要耗費的cpu時間應該是越少越好,AtomicXXX在高併發計數時,大量的cpu時間都浪費會在自旋上了,這很浪費,也降低了實際的計數效率。

說LongAdder比在高併發時比AtomicLong更高效,這麼說有什麼依據呢?LongAdder是根據ConcurrentHashMap這類爲併發設計的類的基本原理——鎖分段,來實現的,它裏面維護一組按需分配的計數單元,併發計數時,不同的線程可以在不同的計數單元上進行計數,這樣減少了線程競爭,提高了併發效率。本質上是用空間換時間的思想,不過在實際高併發情況中消耗的空間可以忽略不計。

現在,在處理高併發計數時,應該優先使用LongAdder,而不是繼續使用AtomicLong。當然,線程競爭很低的情況下進行計數,使用Atomic還是更簡單更直接,並且效率稍微高一些。其他情況,比如序號生成,這種情況下需要準確的數值,全局唯一的AtomicLong纔是正確的選擇,此時不應該使用LongAdder。

實現原理:對於多個線程對同一個變量操作不相互排斥,而是保存每個線程對變量的修改,在讀取變量的時候進行彙總,這樣就不會造成線程間的互斥和重試,極大提高想併發性能。

AtomicReference

AtomicReference類提供了一種讀和寫都是原子性的對象引用變量。原子意味着多個線程試圖改變同一個AtomicReference(例如比較和交換操作)將不會使得AtomicReference處於不一致的狀態。AtomicReferenc的compareAndSet()方法可以使得它與期望的一個值進行比較,如果他們是相等的,AtomicReference裏的對象會被設置成一個新的引用。

下面是AtomicReference compareAndSet()方法的例子:

String initialReference = "initial value referenced";

AtomicReference<String> atomicStringReference =
    new AtomicReference<String>(initialReference);

String newReference = "new value referenced";
boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

exchanged = atomicStringReference.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

AtomicReferenceFieldUpdater

基於反射的實用工具,可以對指定類的指定 volatile 字段進行原子更新。該類用於原子數據結構,該結構中同一節點的幾個引用字段都獨立受原子更新控制。
簡單的示例:

public class App {  
    public static void main(String[] args) throws Exception {  
        AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.class,String.class,"name");  
        Dog dog1=new Dog();  
        updater.compareAndSet(dog1,dog1.name,"test") ;  
        System.out.println(dog1.name);  
  
    }  
  
}  
  
class Dog  {  
    volatile  String name="dog1";  
}  

AtomicStampedReference

這個類用於解決CAS中的ABA問題。它內部不僅維護了對象值,還維護了一個版本號(我這裏把它稱爲版本號,實際上它可以使任何一個整數,它使用整數來表示狀態值)。當AtomicStampedReference對應的數值被修改時,除了更新數據本身外,還必須要更新版本號。當AtomicStampedReference設置對象值時,對象值以及版本號都必須滿足期望值,寫入纔會成功。因此,即使對象值被反覆讀寫,寫回原值,只要版本號發生變化,就能防止不恰當的寫入。

public static void main(String[] args) {

        String str1 = "aaa";
        String str2 = "bbb";
        AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1,1);
        reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1);
        System.out.println("reference.getReference() = " + reference.getReference());

        boolean b = reference.attemptStamp(str2, reference.getStamp() + 1);
        System.out.println("b: "+b);
        System.out.println("reference.getStamp() = "+reference.getStamp());

        boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1);
        System.out.println("reference.getReference() = "+reference.getReference());
        System.out.println("c = " + c);
    }
輸出:
reference.getReference() = bbb
b: true
reference.getStamp() = 3
reference.getReference() = bbb
c = false
c爲什麼輸出false, 因爲版本戳不一致啦

AtomicLongArray

內部維護了一個數組,提供方法能夠CAS更改數組的某個下標的值。

總結

atomic包中的類都是通過CAS的操作完成線程安全的更新值。瞭解這些類可以在需要的時候進行使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章