本篇看一下Volatile關(guān)鍵字和原子引用。

上圖就是JUC包結(jié)構(gòu),總共分成三塊
(1)java.util.concurrent:并發(fā)包基礎(chǔ)類(lèi),包括阻塞隊(duì)列,線程池相關(guān)類(lèi),線程安全Map等。
(2)java.util.concurrent.atomic:原子引用相關(guān)類(lèi)
(3)java.util.concurrent.locks:線程鎖相關(guān)類(lèi)
線程池技術(shù)在之前的講解應(yīng)該很清楚了,今天主要解析一個(gè)volatile關(guān)鍵字以及原子引用的相關(guān)類(lèi)。這一篇文件涉及到JMM(java內(nèi)存模型)的知識(shí),之前也有講解。

一、volatile
這個(gè)關(guān)鍵字,是JVM提供的輕量級(jí)的同步機(jī)制??赡茉跇I(yè)務(wù)功能開(kāi)發(fā)中很少涉及,但其是JUC包里面擁有重要地位的大哥。
首先看下理論:
(1)保證變量?jī)?nèi)存可見(jiàn)性:就是當(dāng)前線程對(duì)某個(gè)volatile修飾的變量進(jìn)行操作的話會(huì)及時(shí)通知到其它線程。比如現(xiàn)在的在線編輯文檔,當(dāng)某個(gè)人編輯了文檔并保存之后,其他人會(huì)立馬收到文檔已被修改的通知。
(2)***不保證原子性: ***原子性的意思就是某個(gè)元素已經(jīng)是最小的了,不可在被分割,比如現(xiàn)實(shí)中原子就是最小的粒子了,不可能再被分割。而映射到這邊就是說(shuō)某個(gè)操作不可在被分割成兩個(gè)或多個(gè)操作,要么成功,要么失敗。
(3)禁止指令重排:JVM為了獲取最快的運(yùn)行速度,會(huì)在底層一些沒(méi)有先后性要求的指令進(jìn)行一些指令重排的操作,在代碼層面是一步一步執(zhí)行,但在底層執(zhí)行的時(shí)候并不是如此。比如JVM創(chuàng)建了一個(gè)對(duì)象有三步,創(chuàng)建實(shí)例對(duì)象、分配空間、將引用指向內(nèi)存空間,但是第一步和第二步并沒(méi)有什么關(guān)系,這個(gè)時(shí)候就可能會(huì)發(fā)生指令重排的操作。
代碼示例:
(1)可見(jiàn)性代碼驗(yàn)證:
當(dāng)資源類(lèi)變量a未加volatile關(guān)鍵字修飾的時(shí)候
當(dāng)添加了volatile關(guān)鍵字修飾后:
(2)不保證原子性驗(yàn)證:
如果保證原子性,那么最后輸出的結(jié)果應(yīng)該是20000,而真實(shí)輸出是19734,所以這可以 證明其不保證原子性。
JVM為了提供了可保證原子性的原子類(lèi)。
二、原子類(lèi)
下圖就是原子類(lèi)的結(jié)構(gòu)圖
先用AtomicInteger類(lèi)是否可以保證原子性。不多說(shuō),上圖片。
可以看到,用原子類(lèi)AtomicInteger類(lèi)是可以獲取期望值的,也就是保證了原子性。
接下來(lái)看一下原子類(lèi)底層是怎么樣的。
上圖可以看到,有三個(gè)變量,分別是value,valueOffset,unsafe。
(1)value:是volatile關(guān)鍵字修飾的,也就是說(shuō)原子類(lèi)底層也是volatile變量。
(2)valueOffset:當(dāng)前變量的內(nèi)存位移值。
(3)unsafe:下圖可以看到,unsafe類(lèi)里面都是native修飾的本地方法,是直接調(diào)用其他語(yǔ)言進(jìn)行操作變量的。
看一下原子類(lèi)的addAndGet方法為什么能實(shí)現(xiàn)原子性呢?
點(diǎn)進(jìn)去會(huì)看到它調(diào)用了unsafe的getAndAddInt方法。
繼續(xù)點(diǎn)下去會(huì)看到如下方法,里面有個(gè)compareAndSwap。
介紹一個(gè)compareAndSwap方法,簡(jiǎn)稱(chēng)CAS(比較并交換),其有三個(gè)值,內(nèi)存值V,舊的預(yù)估值A(chǔ),更新值B。只有當(dāng)V和A相等的時(shí)候,才會(huì)將變量的的值修改為B,否則什么都不干。
可以看到,上述的getAndAddInt方法是個(gè)自旋鎖的實(shí)現(xiàn),一直調(diào)用getIntVolatile(也是本地方法)方法獲取對(duì)象的最新值,獲取之后就會(huì)調(diào)用CAS方法。
CAS:原稱(chēng)為Compare-And-Swap,比較并交換,是一條cpu的并發(fā)原語(yǔ),它的功能是判斷內(nèi)存某個(gè)位置的值是否為預(yù)期值,如果是則更改為預(yù)期值,這個(gè)過(guò)程是原子的。
并發(fā)原語(yǔ)體現(xiàn)在調(diào)用unsafe的方法。原語(yǔ)是屬于操作系統(tǒng)的范疇,原語(yǔ)的執(zhí)行必須是聯(lián)系的,也就是原子性,不會(huì)造成所謂的數(shù)據(jù)不一致的問(wèn)題。(這個(gè)我也不是很懂,畢竟我不是科班出身的程序員,應(yīng)該是要學(xué)操作系統(tǒng)才會(huì)明白)
(如果大廠面試的時(shí)候讓你手寫(xiě)一個(gè)自旋鎖,就把上述代碼糊他臉上)
上述就是其保證原子性的方法,利用自旋鎖和unsafe類(lèi)。
說(shuō)完原子類(lèi)的底層,進(jìn)行拓展一下:上述可以看到,原子類(lèi)java只提供了基本類(lèi)型的封裝,例如AtomicInteger,AtomicLong等,但是工作中需要很多其他自定義的實(shí)體,也要保證原子性,該怎么辦呢?
別慌,JVM還提供了原子引用類(lèi)型AtomicReference,下圖可見(jiàn)此類(lèi)是一個(gè)泛型類(lèi)。
下次會(huì)帶大家看一下CAS導(dǎo)致的ABA問(wèn)題。
=======================================================
我是Liusy,一個(gè)喜歡健身的程序員。
歡迎關(guān)注【Liusy01】,一起交流Java技術(shù)及健身,獲取更多干貨。