一文讀懂 Java 中的原子類

1、無鎖方案

Java 併發包中的原子類都是基於無鎖方案實現的,相較於傳統的互斥鎖,無鎖並無加鎖、解鎖、線程切換的消耗,所以無鎖解決方案的性能更好,同時無鎖還可以保證線程安全。java

1. 無鎖方案的實現原理

無鎖主要依賴 CAS(Compare And Swap) ,即比較並交換,CAS 是一條 CPU 指令,其自己是可以保證原子性的。CAS 中有三個參數:數組

  • 共享變量的內存地址 A
  • 用於比較的值 B
  • 共享變量的新值 C
public class SimpleCAS {

    private int value;

    public synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

上面的代碼展現了 CAS 的簡單實現,從內存中讀出當前 value 的值,而且須要判斷,指望值 expectVal == curVal 的時候,纔會將 value 更新爲新值。安全

仍然以上面的代碼,來實現一個簡單的,基於 CAS 的線程安全的 value+1 方法。這裏的 cas 方法僅用於幫助理解,因此執行結果可能有出入。併發

public class SimpleCAS {

    private volatile int value;

    public void addValue(){
        int newVal = value + 1;
        while (value != cas(value, newVal)){
            newVal = value + 1;
        }
    }
    
    private synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

線程首先讀取 value 的值並加 1,若是此時有另外一個線程更新了 value,則指望值和 value 不相等,更新失敗。更新失敗後,循環嘗試,從新讀取 value 的值,直到更新成功退出循環。函數

2. ABA 問題

無鎖的實現方案中須要注意的一個問題即是 ABA 問題。性能

例如上面的代碼,value 的初始值爲 0,線程 t1 取到了 value 的值,並將其更新爲 1,而後線程又將 value 更新爲 0。ui

假如這個過程當中有另一個線程 t2,和 t1 同時取初始值爲 0 的 value,t2 在 t1 執行完後更新 value,這個時候 value 雖然仍是爲 0,但已經被 t1 修改過了。線程

大多數狀況下,咱們並不須要關心 ABA 問題,例如數值型數據的加減,可是對象類型的數據遇到了 ABA 問題的話,可能先後的屬性已經發生了變化,因此須要解決。code

解決的辦法也很簡單,給對象類型的數據加上一個版本號便可,每更新一次,版本號加 1,這樣即便對象數據從 A 變成 B 後 又變成 A,可是版本號是遞增的,就能夠分辨出對象仍是被修改過的。對象

2、原子類

1. 原子化基本數據類型

有三個實現類:AtomicBoolean、AtomicInteger、AtomicLong

經常使用的方法以下,以 AtomicInteger 爲例,其餘的相似:

AtomicInteger i = new AtomicInteger(0);

i.getAndSet(int newValue);//獲取當前值並設置新值

i.getAndIncrement();//至關於 i ++
i.incrementAndGet();//至關於 ++ i

i.getAndDecrement();//至關於 i --
i.decrementAndGet();//至關於 -- i

i.addAndGet(int delta);//至關於 i + delta,並返回添加後的值
i.getAndAdd(int delta);//至關於 i + delta,並返回添加前的值

i.compareAndSet(int expect, int update);//CAS 操做,返回 boolean值,表示是否更新成功

i.getAndUpdate(update -> 10);//經過函數更新值
i.updateAndGet(update -> 10);//相似上面

2. 原子化對象引用類型

實現類分別是:AtomicReference、AtomicStampedReference、AtomicMarkableReference,其中後兩個能夠實現瞭解決 ABA 問題的方案。

AtomicReference 經常使用的方法以下:

//假設有一個叫作 Order 的類
AtomicReference<Order> orderReference = new AtomicReference<>();

orderReference.getAndSet(Order newValue);//獲取並設置

orderReference.set(Order order);//設置值
Order order1 = orderReference.get();//獲取對象

orderReference.compareAndSet(Order expect, Order update);//比較交換

orderReference.getAndUpdate();//經過函數更新值
orderReference.updateAndGet();

AtomicStampedReference 須要傳入初始值和初始 stamp,其中 stamp 至關於對象的版本號(用來解決 ABA 問題),使用示例以下:

AtomicStampedReference<String> reference = new AtomicStampedReference<>("roseduan", 0);

//嘗試修改stamp的值
boolean b = reference.attemptStamp(reference.getReference(), 10);

//獲取值
String str = reference.getReference();

//獲取stamp
int stamp = reference.getStamp();

//從新設置值和stamp
reference.set("I am not roseduan", 20);

//比較交換
boolean b1 = reference.compareAndSet("roseduan", "jack", 20, 0);

AtomicMarkableReference 使用一個 mark 標記(boolean 類型) 代替了 AtomicStampedReference 中的 stamp,用這種更簡單的方式來解決 ABA 問題。使用的方式和上面的相似,只是將方法中的 stamp 變爲 boolean 類型的值便可。

3. 原子化數組類型

實現類有三個:

  • AtomicIntegerArray:原子化的整型數組
  • AtomicLongArray:原子化長整型數組
  • AtomicReferenceArray:原子化對象引用數組

使用和原子化基本類型都是差很少的,只是須要在方法中加上數組下標便可。

4. 原子化對象屬性更新器

也有三個實現類:

  • AtomicIntegerFieldUpdater:更新對象的整型屬性
  • AtomicLongFieldUpdater:更新對象的長整型屬性
  • AtomicReferenceFieldUpdater:更新對象的引用型屬性

這三個類都是利用 Java 的反射機制實現的,而且爲了保證原子性,要求被更新的對象的屬性必須是 volatile 類型的。使用示例以下:

@Data
@Builder
public class User {

    private volatile int age;

    private volatile long number;

    private volatile String name;

    public static void main(String[] args) {
        User user = User.builder().age(22).number(15553663L).name("roseduan").build();

        //更新age屬性的值
        AtomicIntegerFieldUpdater<User> integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
        integerFieldUpdater.set(user, 25);

        //更新number屬性的值
        AtomicLongFieldUpdater<User> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(User.class, "number");
        longFieldUpdater.set(user, 1000101L);

        //更新對象類型的屬性的值
        AtomicReferenceFieldUpdater<User, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
        referenceFieldUpdater.set(user, "I am not roseduan");

        System.out.println(user.toString());
    }
}

程序中建立了一個 User 類,有三個屬性 age、number、name 分別對應整型、長整型、引用類型。而後使用對象屬性更新器進行屬性值的更新,更新器的其餘方法的使用和前面說到的幾種原子化類型相似。

5. 原子化累加器

實現類有四個:

  • DoubleAdder
  • DoubleAccumulator
  • LongAdder
  • LongAccumulator

這幾個類的功能有限,僅用來執行累加操做,可是速度很是快。下面介紹 DoubleAdder 和 DoubleAccumulator 的用法,其他兩個相似。

//DoubleAccumulator使用示例
DoubleAccumulator a = new DoubleAccumulator(Double::sum, 0);//設初始值爲0
//累加
a.accumulate(1);
a.accumulate(2);
a.accumulate(3);
a.accumulate(4);

System.out.println(a.get());//輸出10

//DoubleAdder使用示例
DoubleAdder adder = new DoubleAdder();
adder.add(1);
adder.add(2);
adder.add(3);
adder.add(4);
adder.add(5);

System.out.println(adder.intValue());//輸出15
相關文章
相關標籤/搜索