摘要:但是,有些操作會依賴于對象的變化過程,此時的解決思路一般就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么就會變成。四的引入就是上面所說的加了版本號的。
本文首發于一世流云的專欄:https://segmentfault.com/blog...一、AtomicReference簡介
AtomicReference,顧名思義,就是以原子方式更新對象引用。
可以看到,AtomicReference持有一個對象的引用——value,并通過Unsafe類來操作該引用:
為什么需要AtomicReference?難道多個線程同時對一個引用變量賦值也會出現并發問題?
引用變量的賦值本身沒有并發問題,也就是說對于引用變量var ,類似下面的賦值操作本身就是原子操作:
Foo var = ... ;
AtomicReference的引入是為了可以用一種類似樂觀鎖的方式操作共享資源,在某些情景下以提升性能。
我們知道,當多個線程同時訪問共享資源時,一般需要以加鎖的方式控制并發:
volatile Foo sharedValue = value; Lock lock = new ReentrantLock(); lock.lock(); try{ // 操作共享資源sharedValue } finally{ lock.unlock(); }
上述訪問方式其實是一種對共享資源加悲觀鎖的訪問方式。
而AtomicReference提供了以無鎖方式訪問共享資源的能力,看看如何通過AtomicReference保證線程安全,來看個具體的例子:
public class AtomicRefTest { public static void main(String[] args) throws InterruptedException { AtomicReferenceref = new AtomicReference<>(new Integer(1000)); List list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { Thread t = new Thread(new Task(ref), "Thread-" + i); list.add(t); t.start(); } for (Thread t : list) { t.join(); } System.out.println(ref.get()); // 打印2000 } } class Task implements Runnable { private AtomicReference ref; Task(AtomicReference ref) { this.ref = ref; } @Override public void run() { for (; ; ) { //自旋操作 Integer oldV = ref.get(); if (ref.compareAndSet(oldV, oldV + 1)) // CAS操作 break; } } }
上述示例,最終打印“2000”。
該示例并沒有使用鎖,而是使用自旋+CAS的無鎖操作保證共享變量的線程安全。1000個線程,每個線程對金額增加1,最終結果為2000,如果線程不安全,最終結果應該會小于2000。
通過示例,可以總結出AtomicReference的一般使用模式如下:
AtomicReference
上面的代碼模板就是AtomicReference的常見使用方式,看下compareAndSet方法:
該方法會將入參的expect變量所指向的對象和AtomicReference中的引用對象進行比較,如果兩者指向同一個對象,則將AtomicReference中的引用對象重新置為update,修改成功返回true,失敗則返回false。也就是說,AtomicReference其實是比較對象的引用。
二、AtomicReference接口/類聲明 類聲明 接口聲明 三、CAS操作可能存在的問題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對象的操作。AtomicStampedReference使用示例
和AtomicReference相比,AtomicStampedReference中的每個引用變量都帶上了pair.stamp這個版本號,這樣就可以解決CAS中的ABA問題了。
來看下AtomicStampedReference的使用:
AtomicStampedReferenceasr = 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即可。
AtomicStampedReference接口聲明 四、AtomicMarkableReference我們在講ABA問題的時候,引入了AtomicStampedReference。
AtomicStampedReference可以給引用加上版本號,追蹤引用的整個變化過程,如:
A -> B -> C -> D - > A,通過AtomicStampedReference,我們可以知道,引用變量中途被更改了3次。
但是,有時候,我們并不關心引用變量更改了幾次,只是單純的關心是否更改過,所以就有了AtomicMarkableReference:
可以看到,AtomicMarkableReference的唯一區別就是不再用int標識引用,而是使用boolean變量——表示引用變量是否被更改過。
從語義上講,AtomicMarkableReference對于那些不關心引用變化過程,只關心引用變量是否變化過的應用會更加友好。
AtomicMarkableReference接口聲明文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76556.html
摘要:整個包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據一系列常見的多線程設計模式,設計了并發包,其中包下提供了一系列基礎的鎖工具,用以對等進行補充增強。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發于一世流云專欄:https...
摘要:注意原子數組并不是說可以讓線程以原子方式一次性地操作數組中所有元素的數組。類的方法返回指定類型數組的元素所占用的字節數。,是將轉換為進制,然后從左往右數連續的個數。 showImg(https://segmentfault.com/img/remote/1460000016012145); 本文首發于一世流云的專欄:https://segmentfault.com/blog... 一...
摘要:本身不直接支持指針的操作,所以這也是該類命名為的原因之一。中的許多方法,內部其實都是類在操作。 showImg(https://segmentfault.com/img/remote/1460000016012251); 本文首發于一世流云的專欄:https://segmentfault.com/blog... 一、Unsafe簡介 在正式的開講 juc-atomic框架系列之前,有...
摘要:所謂,就是可以以一種線程安全的方式操作非線程安全對象的某些字段。我們來對上述代碼進行改造賬戶類改造引入通過操作字段調用方,并未做任何改變上述代碼,無論執行多少次,最終結果都是,因為這回是線程安全的。這也是整個包的設計理念之一。 showImg(https://segmentfault.com/img/remote/1460000016012109); 本文首發于一世流云的專欄:http...
摘要:顧名思義,是類型的線程安全原子類,可以在應用程序中以原子的方式更新值。創建對象先來看下對象的創建。也就是說當一個線程修改一個共享變量時,其它線程能立即讀到這個修改的值。 showImg(https://segmentfault.com/img/remote/1460000016012210); 本文首發于一世流云的專欄:https://segmentfault.com/blog... ...
閱讀 2562·2021-09-02 15:40
閱讀 1566·2019-08-30 15:54
閱讀 1080·2019-08-30 12:48
閱讀 3398·2019-08-29 17:23
閱讀 1046·2019-08-28 18:04
閱讀 3664·2019-08-26 13:54
閱讀 606·2019-08-26 11:40
閱讀 2391·2019-08-26 10:15