溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java三大性質(zhì)總結(jié):原子性、可見性以及有序性

發(fā)布時(shí)間:2020-07-19 21:48:56 來源:網(wǎng)絡(luò) 閱讀:1065 作者:Java筆記丶 欄目:編程語言

本人免費(fèi)整理了Java高級資料,涵蓋了Java、RedisMongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并發(fā)分布式等教程,一共30G,需要自己領(lǐng)取。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

1. 三大性質(zhì)簡介
在并發(fā)編程中分析線程安全的問題時(shí)往往需要切入點(diǎn),那就是兩大核心:JMM抽象內(nèi)存模型以及happens-before規(guī)則Java內(nèi)存模型以及happens-before規(guī)則,三條性質(zhì):原子性,有序性和可見性。關(guān)于synchronized和volatile已經(jīng)討論過了,就想著將并發(fā)編程中這兩大神器在?原子性,有序性和可見性上做一個(gè)比較,當(dāng)然這也是面試中的高頻考點(diǎn),值得注意。


2. 原子性
原子性是指一個(gè)操作是不可中斷的,要么全部執(zhí)行成功要么全部執(zhí)行失敗,有著“同生共死”的感覺。及時(shí)在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開始,就不會被其他線程所干擾。我們先來看看哪些是原子操作,哪些不是原子操作,有一個(gè)直觀的印象:

?int?a?=?10;??//1
?a++;??//2
?int?b=a;?//3
?a?=?a+1;?//4


上面這四個(gè)語句中只有第1個(gè)語句是原子操作,將10賦值給線程工作內(nèi)存的變量a,而語句2(a++),實(shí)際上包含了三個(gè)操作:1. 讀取變量a的值;2:對a進(jìn)行加一的操作;3.將計(jì)算后的值再賦值給變量a,而這三個(gè)操作無法構(gòu)成原子操作。對語句3,4的分析同理可得這兩條語句不具備原子性。當(dāng)然,java內(nèi)存模型中定義了8中操作都是原子的,不可再分的。

  1. lock(鎖定):作用于主內(nèi)存中的變量,它把一個(gè)變量標(biāo)識為一個(gè)線程獨(dú)占的狀態(tài);

  2. unlock(解鎖):作用于主內(nèi)存中的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定

  3. read(讀?。鹤饔糜谥鲀?nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便后面的load動作使用;

  4. load(載入):作用于工作內(nèi)存中的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存中的變量副本

  5. use(使用):作用于工作內(nèi)存中的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會執(zhí)行這個(gè)操作;

  6. assign(賦值):作用于工作內(nèi)存中的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作;

  7. store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送給主內(nèi)存中以便隨后的write操作使用;

  8. write(操作):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

上面的這些指令操作是相當(dāng)?shù)讓拥?,可以作為擴(kuò)展知識面掌握下。那么如何理解這些指令了?比如,把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存中就需要執(zhí)行read,load操作,將工作內(nèi)存同步到主內(nèi)存中就需要執(zhí)行store,write操作。

注意的是:java內(nèi)存模型只是要求上述兩個(gè)操作是順序執(zhí)行的并不是連續(xù)執(zhí)行的。也就是說read和load之間可以插入其他指令,store和writer可以插入其他指令。比如對主內(nèi)存中的a,b進(jìn)行訪問就可以出現(xiàn)這樣的操作順序:read a,read b, load b,load a。


由原子性變量操作read,load,use,assign,store,write,可以大致認(rèn)為基本數(shù)據(jù)類型的訪問讀寫具備原子性(例外就是long和double的非原子性協(xié)定)


synchronized
上面一共有八條原子操作,其中六條可以滿足基本數(shù)據(jù)類型的訪問讀寫具備原子性,還剩下lock和unlock兩條原子操作。如果我們需要更大范圍的原子性操作就可以使用lock和unlock原子操作。

盡管jvm沒有把lock和unlock開放給我們使用,但jvm以更高層次的指令monitorenter和monitorexit指令開放給我們使用,反應(yīng)到j(luò)ava代碼中就是---synchronized關(guān)鍵字,也就是說synchronized滿足原子性。
volatile 我們先來看這樣一個(gè)例子:

public?class?VolatileExample?{
????private?static?volatile?int?counter?=?0;

????public?static?void?main(String[]?args)?{
????????for?(int?i?=?0;?i?<?10;?i++)?{
????????????Thread?thread?=?new?Thread(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????for?(int?i?=?0;?i?<?10000;?i++)
????????????????????????counter++;
????????????????}
????????????});
????????????thread.start();
????????}
????????try?{
????????????Thread.sleep(1000);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????System.out.println(counter);
????}
}


開啟10個(gè)線程,每個(gè)線程都自加10000次,如果不出現(xiàn)線程安全的問題最終的結(jié)果應(yīng)該就是:10*10000 = 100000;可是運(yùn)行多次都是小于100000的結(jié)果,問題在于?volatile并不能保證原子性,在前面說過counter++這并不是一個(gè)原子操作,包含了三個(gè)步驟:

1.讀取變量counter的值;

2.對counter加一;

3.將新值賦值給變量counter。

如果線程A讀取counter到工作內(nèi)存后,其他線程對這個(gè)值已經(jīng)做了自增操作后,那么線程A的這個(gè)值自然而然就是一個(gè)過期的值,因此,總結(jié)果必然會是小于100000的。
如果讓volatile保證原子性,必須符合以下兩條規(guī)則:

  1. 運(yùn)算結(jié)果并不依賴于變量的當(dāng)前值,或者能夠確保只有一個(gè)線程修改變量的值;

  2. 變量不需要與其他的狀態(tài)變量共同參與不變約束

3. 有序性
synchronized
synchronized語義表示鎖在同一時(shí)刻只能由一個(gè)線程進(jìn)行獲取,當(dāng)鎖被占用后,其他線程只能等待。因此,synchronized語義就要求線程在訪問讀寫共享變量時(shí)只能“串行”執(zhí)行,因此synchronized具有有序性。


volatile
在java內(nèi)存模型中說過,為了性能優(yōu)化,編譯器和處理器會進(jìn)行指令重排序;也就是說java程序天然的有序性可以總結(jié)為:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程觀察另一個(gè)線程,所有的操作都是無序的。在單例模式的實(shí)現(xiàn)上有一種雙重檢驗(yàn)鎖定的方式(Double-checked Locking)。

代碼如下:

public?class?Singleton?{
????private?Singleton()?{?}
????private?volatile?static?Singleton?instance;
????public?Singleton?getInstance(){
????????if(instance==null){
????????????synchronized?(Singleton.class){
????????????????if(instance==null){
????????????????????instance?=?new?Singleton();
????????????????}
????????????}
????????}
????????return?instance;
????}
}


這里為什么要加volatile了?我們先來分析一下不加volatile的情況,有問題的語句是這條:
instance = new Singleton();

這條語句實(shí)際上包含了三個(gè)操作:

1.分配對象的內(nèi)存空間;

2.初始化對象;

3.設(shè)置instance指向剛分配的內(nèi)存地址。

但由于存在重排序的問題,可能有以下的執(zhí)行順序:

Java三大性質(zhì)總結(jié):原子性、可見性以及有序性


如果2和3進(jìn)行了重排序的話,線程B進(jìn)行判斷if(instance==null)時(shí)就會為true,而實(shí)際上這個(gè)instance并沒有初始化成功,顯而易見對線程B來說之后的操作就會是錯(cuò)得。

用volatile修飾的話就可以禁止2和3操作重排序,從而避免這種情況。

volatile包含禁止指令重排序的語義,其具有有序性


4. 可見性
可見性是指當(dāng)一個(gè)線程修改了共享變量后,其他線程能夠立即得知這個(gè)修改。通過之前對內(nèi)存synchronzed語義進(jìn)行了分析,當(dāng)線程獲取鎖時(shí)會從主內(nèi)存中獲取共享變量的最新值,釋放鎖的時(shí)候會將共享變量同步到主內(nèi)存中。

從而,synchronized具有可見性。同樣的在volatile分析中,會通過在指令中添加lock指令,以實(shí)現(xiàn)內(nèi)存可見性。因此,?volatile具有可見性


5. 總結(jié)

通過這篇文章,主要是比較了synchronized和volatile在三條性質(zhì):原子性,可見性,以及有序性的情況,

歸納如下:
synchronized: 具有原子性,有序性和可見性;?volatile:具有有序性和可見性


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI