轉載自:https://segmentfault.com/a/1190000015831791?utm_source=tag-newest#articleHeader0
就是以原子方式更新對象引用。
可以看到它持有一個對象的引用,-value,用volatile修飾,並通過unsafe類來操作該引用。
1 private static final Unsafe unsafe = Unsafe.getUnsafe(); 2 private static final long valueOffset; 3 4 static { 5 try { 6 valueOffset = unsafe.objectFieldOffset 7 (AtomicReference.class.getDeclaredField("value")); 8 } catch (Exception ex) { throw new Error(ex); } 9 } 10 11 private volatile V value; 12 13 /** 14 * Creates a new AtomicReference with the given initial value. 15 * 16 * @param initialValue the initial value 17 */ 18 public AtomicReference(V initialValue) { 19 value = initialValue; 20 } 21 22 /** 23 * Creates a new AtomicReference with null initial value. 24 */ 25 public AtomicReference() { 26 }
為什么需要AtomicReference?難道多個線程同時對一個引用變量賦值也會出現並發問題?
引用變量的賦值本身沒有並發問題,也就是說對於引用變量var ,類似下面的賦值操作本身就是原子操作:Foo var = ... ;
AtomicReference的引入是為了可以用一種類似樂觀鎖的方式操作共享資源,在某些情景下以提升性能。
我們知道,當多個線程同時訪問共享資源時,一般需要以加鎖的方式控制並發:
volatile Foo sharedValue = value; Lock lock = new ReentrantLock(); lock.lock(); try{ // 操作共享資源sharedValue } finally{ lock.unlock(); }
上述訪問方式其實是一種對共享資源加悲觀鎖的訪問方式。
而AtomicReference提供了以無鎖方式訪問共享資源的能力,看看如何通過AtomicReference保證線程安全,來看個具體的例子:
1 package com.citi.test.mutiplethread.demo0503; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.atomic.AtomicReference; 6 7 public class AtomicReferenceTest { 8 public static void main(String[] args) throws InterruptedException { 9 AtomicReference<Integer> ref=new AtomicReference<Integer>(new Integer(1000)); 10 List<Thread> list=new ArrayList<Thread>(); 11 for(int i=0;i<1000;i++){ 12 Thread t=new Thread(new Task(ref),"Thread-"+i); 13 list.add(t); 14 t.start(); 15 } 16 for(Thread t: list){ 17 System.out.println(t.getName()); 18 t.join(); 19 } 20 System.out.println(ref.get()); 21 } 22 } 23 class Task implements Runnable{ 24 private AtomicReference<Integer> ref; 25 public Task(AtomicReference<Integer> ref) { 26 this.ref=ref; 27 } 28 @Override 29 public void run() { 30 for(;;){ 31 Integer oldV=ref.get(); 32 System.out.println(Thread.currentThread().getName()+":"+oldV); 33 if(ref.compareAndSet(oldV, oldV+1)){ 34 break; 35 } 36 } 37 } 38 }
上述示例,最終打印“2000”。
該示例並沒有使用鎖,而是使用自旋+CAS的無鎖操作保證共享變量的線程安全。1000個線程,每個線程對金額增加1,最終結果為2000,如果線程不安全,最終結果應該會小於2000。
通過示例,可以總結出AtomicReference的一般使用模式如下:
AtomicReference<Object> ref = new AtomicReference<>(new Object()); Object oldCache = ref.get(); // 對緩存oldCache做一些操作 Object newCache = someFunctionOfOld(oldCache); // 如果期間沒有其它線程改變了緩存值,則更新 boolean success = ref.compareAndSet(oldCache , newCache);
上面的代碼模板就是AtomicReference的常見使用方式,看下compareAndSet方法:

該方法會將入參的expect變量所指向的對象和AtomicReference中的引用對象進行比較,如果兩者指向同一個對象,則將AtomicReference中的引用對象重新置為update,修改成功返回true,失敗則返回false。也就是說,AtomicReference其實是比較對象的引用。
CAS可能存在ABA的問題。
CAS操作可能存在 ABA的問題,就是說:
假如一個值原來是A,變成了B,又變成了A,那么CAS檢查時會發現它的值沒有發生變化,但是實際上卻變化了。
一般來講這並不是什么問題,比如數值運算,線程其實根本不關心變量中途如何變化,只要最終的狀態和預期值一樣即可。
但是,有些操作會依賴於對象的變化過程,此時的解決思路一般就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A - 2B - 3A。
四、AtomicStampedReference的引入
AtomicStampedReference就是上面所說的加了版本號的AtomicReference。
AtomicStampedReference原理
先來看下如何構造一個AtomicStampedReference對象,AtomicStampedReference只有一個構造器:

可以看到,除了傳入一個初始的引用變量initialRef外,還有一個initialStamp變量,initialStamp其實就是版本號(或者說時間戳),用來唯一標識引用變量。
在構造器內部,實例化了一個Pair對象,Pair對象記錄了對象引用和時間戳信息,采用int作為時間戳,實際使用的時候,要保證時間戳唯一(一般做成自增的),如果時間戳如果重復,還會出現ABA的問題。
AtomicStampedReference的所有方法,其實就是Unsafe類針對這個 Pair對象的操作。
和AtomicReference相比,AtomicStampedReference中的每個引用變量都帶上了pair.stamp這個版本號,這樣就可以解決CAS中的ABA問題了。
AtomicStampedReference使用示例
來看下AtomicStampedReference的使用:
AtomicStampedReference<Foo> asr = new AtomicStampedReference<>(null,0); // 創建AtomicStampedReference對象,持有Foo對象的引用,初始為null,版本為0 int[] stamp=new int[1]; Foo oldRef = asr.get(stamp); // 調用get方法獲取引用對象和對應的版本號 int oldStamp=stamp[0]; // stamp[0]保存版本號 asr.compareAndSet(oldRef, null, oldStamp, oldStamp + 1) //嘗試以CAS方式更新引用對象,並將版本號+1
上述模板就是AtomicStampedReference的一般使用方式,注意下compareAndSet方法:

我們知道,AtomicStampedReference內部保存了一個pair對象,該方法的邏輯如下:
- 如果AtomicStampedReference內部pair的引用變量、時間戳 與 入參expectedReference、expectedStamp都一樣,說明期間沒有其它線程修改過AtomicStampedReference,可以進行修改。此時,會創建一個新的Pair對象(casPair方法,因為Pair是Immutable類)。
但這里有段優化邏輯,就是如果 newReference == current.reference && newStamp == current.stamp,說明用戶修改的新值和AtomicStampedReference中目前持有的值完全一致,那么其實不需要修改,直接返回true即可。
四、AtomicMarkableReference
我們在講ABA問題的時候,引入了AtomicStampedReference。
AtomicStampedReference可以給引用加上版本號,追蹤引用的整個變化過程,如:
A -> B -> C -> D - > A,通過AtomicStampedReference,我們可以知道,引用變量中途被更改了3次。
但是,有時候,我們並不關心引用變量更改了幾次,只是單純的關心是否更改過,所以就有了AtomicMarkableReference:

可以看到,AtomicMarkableReference的唯一區別就是不再用int標識引用,而是使用boolean變量——表示引用變量是否被更改過。
從語義上講,AtomicMarkableReference對於那些不關心引用變化過程,只關心引用變量是否變化過的應用會更加友好。
