Java 併發包中的原子類都是基於無鎖方案實現的,相較於傳統的互斥鎖,無鎖並無加鎖、解鎖、線程切換的消耗,所以無鎖解決方案的性能更好,同時無鎖還可以保證線程安全。java
無鎖主要依賴 CAS(Compare And Swap) ,即比較並交換,CAS 是一條 CPU 指令,其自己是可以保證原子性的。CAS 中有三個參數:數組
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 的值,直到更新成功退出循環。函數
無鎖的實現方案中須要注意的一個問題即是 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,可是版本號是遞增的,就能夠分辨出對象仍是被修改過的。對象
有三個實現類: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);//相似上面
實現類分別是: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 類型的值便可。
實現類有三個:
使用和原子化基本類型都是差很少的,只是須要在方法中加上數組下標便可。
也有三個實現類:
這三個類都是利用 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 分別對應整型、長整型、引用類型。而後使用對象屬性更新器進行屬性值的更新,更新器的其餘方法的使用和前面說到的幾種原子化類型相似。
實現類有四個:
這幾個類的功能有限,僅用來執行累加操做,可是速度很是快。下面介紹 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