首先來(lái)看看Atomic原子類的結(jié)構(gòu)
一窿凤、Automic概念
automic在java中為JUC(java.util.concurrent)并發(fā)包的一個(gè)子包夯秃。顧名思義衬潦,automic包中包含許多原子類驾锰。
1赏酥、什么是原子類?
原子是不可分割的最小單位,故原子類可認(rèn)為是其操作是不可分割的皂吮。
2、原子類的作用?
提供一種簡(jiǎn)單傲霸、性能高效、線程安全地更新一個(gè)變量的方式。
二蹈集、分析Automic底層邏輯(以AtomicInteger自增為例)
通過(guò)前面volatile的講解支示,大家應(yīng)該也對(duì)為什么要有原子類操作有了一定的了解,那我們都知道,i++自增操作不是原子性的场梆,也就是說(shuō)在高并發(fā)的情況下也許會(huì)發(fā)生重復(fù)覆蓋罐脊,這顯然不是我們想要的恃逻,那么Atomic原子類就是來(lái)解決這個(gè)問(wèn)題的,下面我們可以看來(lái)來(lái)AtomicInteger自增源碼:
可以看到AtomicInteger的自增操作返回unsafe.getAndAddInt()方法。
這里大家可能有疑問(wèn)了甲抖,什么是unsafe呢厅篓?
對(duì)于JVM有過(guò)了解的小伙伴可能知道JNI澳盐,也就是java本地庫(kù)接口,Unsafe(sun.misc.Unsafe)是屬于JNI的類,Unsafe里面的native方法直接操作內(nèi)存扼褪,getUnfate()僅提供高級(jí)的Bootstrap類加載器使用,簡(jiǎn)而言之就是直接操作CPU砂代;
接下來(lái)查看unsafe.getAndAddInt()方法源碼
可以發(fā)現(xiàn)我們?cè)赿o...while的循環(huán)條件調(diào)用了compareAndSwapInt(var1, var2, var5, var5 + var4)方法,即AtomicInteger中的CAS算法。
PS:此處對(duì)于CAS(CompareAndSwap)以及之前的volatile不熟悉的同學(xué)可以看看我的另外一篇文章叫搁,那篇文章更適合在看JUC之前了解為什么要學(xué)習(xí)JUC,思路會(huì)更清晰卧波。
三、CAS
CAS是計(jì)算機(jī)硬件對(duì)并發(fā)操作共享數(shù)據(jù)的支持望忆,CAS包含三個(gè)操作數(shù):
1、內(nèi)存值V(var1,var2)
2、預(yù)估值A(chǔ)(var 5)
3梗劫、更新值B(var5 + var4)
只有當(dāng)V==A時(shí)截碴,才會(huì)把B的值賦給V,即V=B晒旅,否則不做任何操作废恋。
不用CAS時(shí)蚓哩,線程修改數(shù)據(jù)曹阔,有從主內(nèi)存取得數(shù)據(jù)、修改數(shù)據(jù)炭序、回寫數(shù)據(jù)這三步豆茫,在回寫數(shù)據(jù)這一步侨歉,可能主內(nèi)存已經(jīng)被別的線程回寫過(guò)了,就會(huì)發(fā)生回寫覆蓋揩魂。為了解決這個(gè)幽邓,CAS在回寫的時(shí)候會(huì)將主內(nèi)存與自己工作內(nèi)存之前取得的值比較一下,就可以判斷是否已經(jīng)被別人修改過(guò)了火脉,如果沒有牵舵,我就可以修改,不過(guò)別人修改過(guò)了倦挂,我再去一次畸颅,在最新的值上面再修改。
為什么用CAS不用Synchronized呢方援?
鎖之后没炒,最耗時(shí)的就是線程的上下文交換,也就是線程被掛起和被喚醒的過(guò)程犯戏,在這個(gè)過(guò)程中送火,操作系統(tǒng)會(huì)在核態(tài)和用戶態(tài)之間切換,要保存運(yùn)行環(huán)境和恢復(fù)運(yùn)行環(huán)境先匪,這些都是很費(fèi)事的种吸,在JAVA1.6之后優(yōu)化的自旋鎖就是為了避免上下文交換。
PS:關(guān)于操作系統(tǒng)核態(tài)和用戶態(tài)是什么以及怎樣切換面試也問(wèn)的挺多的呀非,感興趣的同學(xué)可以看看我操作系統(tǒng)相關(guān)的文章和面經(jīng)坚俗。
通過(guò)上面的分析可以看到,do,while循環(huán)也避免了線程的上下文交換,性能會(huì)高很多猖败。
CAS的缺點(diǎn)
1形耗、雖然CAS沒有掛起線程、增加并發(fā)性辙浑,但是當(dāng)很多個(gè)線程進(jìn)行自旋,就會(huì)白白耗費(fèi)CPU資源拟糕,這種并發(fā)判呕,就只是讓很多線程在那循環(huán)等待。
2送滞、只能保證一個(gè)共享變量的原子操作侠草。CAS要有一個(gè)compare,一個(gè)變量就好對(duì)比犁嗅,如果是段代碼边涕,一個(gè)類呢?synchronized就能鎖這樣的褂微,當(dāng)時(shí)還有AtomicReference等類有相應(yīng)的操作功蜓。
3、ABA問(wèn)題
先有兩個(gè)線程宠蚂,其中一個(gè)運(yùn)行的比較快式撼,將主內(nèi)存中的變量從A改成了B,再?gòu)腂改成了A求厕,這個(gè)時(shí)候另外一個(gè)線程調(diào)用compareAndSwap方法時(shí)著隆,會(huì)發(fā)現(xiàn)期望值于實(shí)際值是相同的,它并不知道中間已經(jīng)被修改過(guò)了呀癣,它就會(huì)修改掉這個(gè)值美浦。
當(dāng)我們的實(shí)際問(wèn)題不能容忍這個(gè)被修改的過(guò)程,這個(gè)問(wèn)題就很嚴(yán)重了项栏,比如會(huì)員卡的問(wèn)題浦辨,用戶消費(fèi)20元,緊接著再充值20元忘嫉,如果積分統(tǒng)計(jì)線程是根據(jù)余額的減少來(lái)統(tǒng)計(jì)積分的荤牍,那就嚴(yán)重了,積分統(tǒng)計(jì)線程并不知道用戶已經(jīng)消費(fèi)過(guò)了庆冕,積分沒統(tǒng)計(jì)上康吵,這是一個(gè)大漏洞。
4访递、如何解決ABA問(wèn)題晦嵌?
在加個(gè)變量(時(shí)間戳)來(lái)表示這個(gè)變量是否被修改過(guò)了,這樣下來(lái),再執(zhí)行 compareAndSet()方法的時(shí)候惭载,不僅僅比較這個(gè)變量是否相同旱函,還要比較時(shí)間戳是否相等。
AtomicStampReference類則實(shí)現(xiàn)了這個(gè)功能
AtomicStampReference構(gòu)造函數(shù)和compareAndSet()方法
這里我們以AtomicInteger為例講解了Atomic原子類的作用描滔,通過(guò)分析AtomicInteger自增源碼我們可以知道Atomic原子類的底層是CAS棒妨,這里對(duì)于CAS也進(jìn)行了較為詳細(xì)的講解,CAS本身也是面試中經(jīng)常會(huì)遇到的問(wèn)題含长,不熟悉的同學(xué)需要進(jìn)行詳細(xì)的了解券腔。