摘要:但是,有些操作會(huì)依賴于對(duì)象的變化過程,此時(shí)的解決思路一般就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么就會(huì)變成。四的引入就是上面所說的加了版本號(hào)的。
本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog...一、AtomicReference簡(jiǎn)介
AtomicReference,顧名思義,就是以原子方式更新對(duì)象引用。
可以看到,AtomicReference持有一個(gè)對(duì)象的引用——value,并通過Unsafe類來操作該引用:
為什么需要AtomicReference?難道多個(gè)線程同時(shí)對(duì)一個(gè)引用變量賦值也會(huì)出現(xiàn)并發(fā)問題?
引用變量的賦值本身沒有并發(fā)問題,也就是說對(duì)于引用變量var ,類似下面的賦值操作本身就是原子操作:
Foo var = ... ;
AtomicReference的引入是為了可以用一種類似樂觀鎖的方式操作共享資源,在某些情景下以提升性能。
我們知道,當(dāng)多個(gè)線程同時(shí)訪問共享資源時(shí),一般需要以加鎖的方式控制并發(fā):
volatile Foo sharedValue = value; Lock lock = new ReentrantLock(); lock.lock(); try{ // 操作共享資源sharedValue } finally{ lock.unlock(); }
上述訪問方式其實(shí)是一種對(duì)共享資源加悲觀鎖的訪問方式。
而AtomicReference提供了以無鎖方式訪問共享資源的能力,看看如何通過AtomicReference保證線程安全,來看個(gè)具體的例子:
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個(gè)線程,每個(gè)線程對(duì)金額增加1,最終結(jié)果為2000,如果線程不安全,最終結(jié)果應(yīng)該會(huì)小于2000。
通過示例,可以總結(jié)出AtomicReference的一般使用模式如下:
AtomicReference
上面的代碼模板就是AtomicReference的常見使用方式,看下compareAndSet方法:
該方法會(huì)將入?yún)⒌?strong>expect變量所指向的對(duì)象和AtomicReference中的引用對(duì)象進(jìn)行比較,如果兩者指向同一個(gè)對(duì)象,則將AtomicReference中的引用對(duì)象重新置為update,修改成功返回true,失敗則返回false。也就是說,AtomicReference其實(shí)是比較對(duì)象的引用。
二、AtomicReference接口/類聲明 類聲明 接口聲明 三、CAS操作可能存在的問題CAS操作可能存在ABA的問題,就是說:
假如一個(gè)值原來是A,變成了B,又變成了A,那么CAS檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。
一般來講這并不是什么問題,比如數(shù)值運(yùn)算,線程其實(shí)根本不關(guān)心變量中途如何變化,只要最終的狀態(tài)和預(yù)期值一樣即可。
但是,有些操作會(huì)依賴于對(duì)象的變化過程,此時(shí)的解決思路一般就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A - 2B - 3A。
四、AtomicStampedReference的引入AtomicStampedReference就是上面所說的加了版本號(hào)的AtomicReference。
AtomicStampedReference原理先來看下如何構(gòu)造一個(gè)AtomicStampedReference對(duì)象,AtomicStampedReference只有一個(gè)構(gòu)造器:
可以看到,除了傳入一個(gè)初始的引用變量initialRef外,還有一個(gè)initialStamp變量,initialStamp其實(shí)就是版本號(hào)(或者說時(shí)間戳),用來唯一標(biāo)識(shí)引用變量。
在構(gòu)造器內(nèi)部,實(shí)例化了一個(gè)Pair對(duì)象,Pair對(duì)象記錄了對(duì)象引用和時(shí)間戳信息,采用int作為時(shí)間戳,實(shí)際使用的時(shí)候,要保證時(shí)間戳唯一(一般做成自增的),如果時(shí)間戳如果重復(fù),還會(huì)出現(xiàn)ABA的問題。
AtomicStampedReference的所有方法,其實(shí)就是Unsafe類針對(duì)這個(gè)Pair對(duì)象的操作。AtomicStampedReference使用示例
和AtomicReference相比,AtomicStampedReference中的每個(gè)引用變量都帶上了pair.stamp這個(gè)版本號(hào),這樣就可以解決CAS中的ABA問題了。
來看下AtomicStampedReference的使用:
AtomicStampedReferenceasr = new AtomicStampedReference<>(null,0); // 創(chuàng)建AtomicStampedReference對(duì)象,持有Foo對(duì)象的引用,初始為null,版本為0 int[] stamp=new int[1]; Foo oldRef = asr.get(stamp); // 調(diào)用get方法獲取引用對(duì)象和對(duì)應(yīng)的版本號(hào) int oldStamp=stamp[0]; // stamp[0]保存版本號(hào) asr.compareAndSet(oldRef, null, oldStamp, oldStamp + 1) //嘗試以CAS方式更新引用對(duì)象,并將版本號(hào)+1
上述模板就是AtomicStampedReference的一般使用方式,注意下compareAndSet方法:
我們知道,AtomicStampedReference內(nèi)部保存了一個(gè)pair對(duì)象,該方法的邏輯如下:
如果AtomicStampedReference內(nèi)部pair的引用變量、時(shí)間戳 與 入?yún)?strong>expectedReference、expectedStamp都一樣,說明期間沒有其它線程修改過AtomicStampedReference,可以進(jìn)行修改。此時(shí),會(huì)創(chuàng)建一個(gè)新的Pair對(duì)象(casPair方法,因?yàn)镻air是Immutable類)。
但這里有段優(yōu)化邏輯,就是如果 newReference == current.reference && newStamp == current.stamp,說明用戶修改的新值和AtomicStampedReference中目前持有的值完全一致,那么其實(shí)不需要修改,直接返回true即可。
AtomicStampedReference接口聲明 四、AtomicMarkableReference我們?cè)谥vABA問題的時(shí)候,引入了AtomicStampedReference。
AtomicStampedReference可以給引用加上版本號(hào),追蹤引用的整個(gè)變化過程,如:
A -> B -> C -> D - > A,通過AtomicStampedReference,我們可以知道,引用變量中途被更改了3次。
但是,有時(shí)候,我們并不關(guān)心引用變量更改了幾次,只是單純的關(guān)心是否更改過,所以就有了AtomicMarkableReference:
可以看到,AtomicMarkableReference的唯一區(qū)別就是不再用int標(biāo)識(shí)引用,而是使用boolean變量——表示引用變量是否被更改過。
從語義上講,AtomicMarkableReference對(duì)于那些不關(guān)心引用變化過程,只關(guān)心引用變量是否變化過的應(yīng)用會(huì)更加友好。
AtomicMarkableReference接口聲明文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76556.html
摘要:整個(gè)包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見的多線程設(shè)計(jì)模式,設(shè)計(jì)了并發(fā)包,其中包下提供了一系列基礎(chǔ)的鎖工具,用以對(duì)等進(jìn)行補(bǔ)充增強(qiáng)。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專欄:https...
摘要:注意原子數(shù)組并不是說可以讓線程以原子方式一次性地操作數(shù)組中所有元素的數(shù)組。類的方法返回指定類型數(shù)組的元素所占用的字節(jié)數(shù)。,是將轉(zhuǎn)換為進(jìn)制,然后從左往右數(shù)連續(xù)的個(gè)數(shù)。 showImg(https://segmentfault.com/img/remote/1460000016012145); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一...
摘要:本身不直接支持指針的操作,所以這也是該類命名為的原因之一。中的許多方法,內(nèi)部其實(shí)都是類在操作。 showImg(https://segmentfault.com/img/remote/1460000016012251); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、Unsafe簡(jiǎn)介 在正式的開講 juc-atomic框架系列之前,有...
摘要:所謂,就是可以以一種線程安全的方式操作非線程安全對(duì)象的某些字段。我們來對(duì)上述代碼進(jìn)行改造賬戶類改造引入通過操作字段調(diào)用方,并未做任何改變上述代碼,無論執(zhí)行多少次,最終結(jié)果都是,因?yàn)檫@回是線程安全的。這也是整個(gè)包的設(shè)計(jì)理念之一。 showImg(https://segmentfault.com/img/remote/1460000016012109); 本文首發(fā)于一世流云的專欄:http...
摘要:顧名思義,是類型的線程安全原子類,可以在應(yīng)用程序中以原子的方式更新值。創(chuàng)建對(duì)象先來看下對(duì)象的創(chuàng)建。也就是說當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí),其它線程能立即讀到這個(gè)修改的值。 showImg(https://segmentfault.com/img/remote/1460000016012210); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... ...
閱讀 2594·2021-09-02 15:40
閱讀 1592·2019-08-30 15:54
閱讀 1114·2019-08-30 12:48
閱讀 3429·2019-08-29 17:23
閱讀 1067·2019-08-28 18:04
閱讀 3688·2019-08-26 13:54
閱讀 634·2019-08-26 11:40
閱讀 2431·2019-08-26 10:15