自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

讀了源碼,發(fā)現(xiàn) Volatile 如此重要!

開發(fā) 前端
最近卷 Eureka 源碼,發(fā)現(xiàn) volatile 在很多地方都用到了,復(fù)習(xí)一波。此文全面講解了 volatile 的用法和細(xì)節(jié),建議收藏轉(zhuǎn)發(fā)后再看。

[[430097]]

大家好,我是悟空。

最近卷 Eureka 源碼,發(fā)現(xiàn) volatile 在很多地方都用到了,復(fù)習(xí)一波。此文全面講解了 volatile 的用法和細(xì)節(jié),建議收藏轉(zhuǎn)發(fā)后再看。

一、Volatile怎么念?

看到這個(gè)單詞一直不知道怎么發(fā)音

英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl]

 

adj. [化學(xué)] 揮發(fā)性的;不穩(wěn)定的;爆炸性的;反復(fù)無常的

那Java中volatile又是干啥的呢?

二、Java中volatile用來干啥?

Volatile是Java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制(三大特性)

  • 保證可見性
  • 不保證原子性
  • 禁止指令重排

要理解三大特性,就必須知道Java內(nèi)存模型(JMM),那JMM又是什么呢?

三、JMM又是啥?

這是一份精心總結(jié)的Java內(nèi)存模型思維導(dǎo)圖,拿去不謝。

原理圖1-Java內(nèi)存模型

3.1 為什么需要Java內(nèi)存模型?

Why:屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異

JMM是Java內(nèi)存模型,也就是Java Memory Model,簡(jiǎn)稱JMM,本身是一種抽象的概念,實(shí)際上并不存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。

3.2 到底什么是Java內(nèi)存模型?

  • 1.定義程序中各種變量的訪問規(guī)則
  • 2.把變量值存儲(chǔ)到內(nèi)存的底層細(xì)節(jié)
  • 3.從內(nèi)存中取出變量值的底層細(xì)節(jié)

3.3 Java內(nèi)存模型的兩大內(nèi)存是啥?

原理圖2-兩大內(nèi)存

  • 主內(nèi)存
    • Java堆中對(duì)象實(shí)例數(shù)據(jù)部分
    • 對(duì)應(yīng)于物理硬件的內(nèi)存
  • 工作內(nèi)存
    • Java棧中的部分區(qū)域
    • 優(yōu)先存儲(chǔ)于寄存器和高速緩存

3.4 Java內(nèi)存模型是怎么做的?

Java內(nèi)存模型的幾個(gè)規(guī)范:

  • 1.所有變量存儲(chǔ)在主內(nèi)存
  • 2.主內(nèi)存是虛擬機(jī)內(nèi)存的一部分
  • 3.每條線程有自己的工作內(nèi)存
  • 4.線程的工作內(nèi)存保存變量的主內(nèi)存副本
  • 5.線程對(duì)變量的操作必須在工作內(nèi)存中進(jìn)行
  • 6.不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量
  • 7.線程間變量值的傳遞均需要通過主內(nèi)存來完成

由于JVM運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為棧空間),工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域,而Java內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對(duì)變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝到自己的工作內(nèi)存空間,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫會(huì)主內(nèi)存,不能直接操作主內(nèi)存中的變量,各個(gè)線程中的工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝,因此不同的線程間無法訪問對(duì)方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成,其簡(jiǎn)要訪問過程:

原理圖3-Java內(nèi)存模型

3.5 Java內(nèi)存模型的三大特性

可見性(當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改)

原子性(一個(gè)操作或一系列操作是不可分割的,要么同時(shí)成功,要么同時(shí)失敗)

有序性(變量賦值操作的順序與程序代碼中的執(zhí)行順序一致)

關(guān)于有序性:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無序的。前半句是指“線程內(nèi)似表現(xiàn)為串行的語義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。

四、能給個(gè)示例說下怎么用volatile的嗎?

考慮一下這種場(chǎng)景:

有一個(gè)對(duì)象的字段number初始化值=0,另外這個(gè)對(duì)象有一個(gè)公共方法setNumberTo100()可以設(shè)置number = 100,當(dāng)主線程通過子線程來調(diào)用setNumberTo100()后,主線程是否知道number值變了呢?

答案:如果沒有使用volatile來定義number變量,則主線程不知道子線程更新了number的值。

(1)定義如上述所說的對(duì)象:ShareData

  1. class ShareData { 
  2.     int number = 0; 
  3.  
  4.     public void setNumberTo100() { 
  5.         this.number = 100; 
  6.     } 

(2)主線程中初始化一個(gè)子線程,名字叫做子線程

子線程先休眠3s,然后設(shè)置number=100。主線程不斷檢測(cè)的number值是否等于0,如果不等于0,則退出主線程。

  1. public class volatileVisibility { 
  2.     public static void main(String[] args) { 
  3.         // 資源類 
  4.         ShareData shareData = new ShareData(); 
  5.  
  6.         // 子線程 實(shí)現(xiàn)了Runnable接口的,lambda表達(dá)式 
  7.         new Thread(() -> { 
  8.  
  9.             System.out.println(Thread.currentThread().getName() + "\t come in"); 
  10.  
  11.             // 線程睡眠3秒,假設(shè)在進(jìn)行運(yùn)算 
  12.             try { 
  13.                 TimeUnit.SECONDS.sleep(3); 
  14.             } catch (InterruptedException e) { 
  15.                 e.printStackTrace(); 
  16.             } 
  17.             // 修改number的值 
  18.             myData.setNumberTo100(); 
  19.  
  20.             // 輸出修改后的值 
  21.             System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number); 
  22.  
  23.         }, "子線程").start(); 
  24.  
  25.         while(myData.number == 0) { 
  26.             // main線程就一直在這里等待循環(huán),直到number的值不等于零 
  27.         } 
  28.  
  29.         // 按道理這個(gè)值是不可能打印出來的,因?yàn)橹骶€程運(yùn)行的時(shí)候,number的值為0,所以一直在循環(huán) 
  30.         // 如果能輸出這句話,說明子線程在睡眠3秒后,更新的number的值,重新寫入到主內(nèi)存,并被main線程感知到了 
  31.         System.out.println(Thread.currentThread().getName() + "\t 主線程感知到了 number 不等于 0"); 
  32.  
  33.         /** 
  34.          * 最后輸出結(jié)果: 
  35.          * 子線程     come in 
  36.          * 子線程     update number value:100 
  37.          * 最后線程沒有停止,并行沒有輸出"主線程知道了 number 不等于0"這句話,說明沒有用volatile修飾的變量,變量的更新是不可見的 
  38.          */ 
  39.     } 

沒有使用volatile

(3)我們用volatile修飾變量number

  1. class ShareData { 
  2.     //volatile 修飾的關(guān)鍵字,是為了增加多個(gè)線程之間的可見性,只要有一個(gè)線程修改了內(nèi)存中的值,其它線程也能馬上感知 
  3.     volatile int number = 0; 
  4.  
  5.     public void setNumberTo100() { 
  6.         this.number = 100; 
  7.     } 

輸出結(jié)果:

  1. 子線程  come in 
  2. 子線程  update number value:100 
  3. main  主線程知道了 number 不等于 0 
  4.  
  5. Process finished with exit code 0 

mark

「小結(jié):說明用volatile修飾的變量,當(dāng)某線程更新變量后,其他線程也能感知到?!?/p>

五、那為什么其他線程能感知到變量更新?

其實(shí)這里就是用到了“窺探(snooping)”協(xié)議。在說“窺探(snooping)”協(xié)議之前,首先談?wù)劸彺嬉恢滦缘膯栴}。

5.1 緩存一致性

當(dāng)多個(gè)CPU持有的緩存都來自同一個(gè)主內(nèi)存的拷貝,當(dāng)有其他CPU偷偷改了這個(gè)主內(nèi)存數(shù)據(jù)后,其他CPU并不知道,那拷貝的內(nèi)存將會(huì)和主內(nèi)存不一致,這就是緩存不一致。那我們?nèi)绾蝸肀WC緩存一致呢?這里就需要操作系統(tǒng)來共同制定一個(gè)同步規(guī)則來保證,而這個(gè)規(guī)則就有MESI協(xié)議。

如下圖所示,CPU2 偷偷將num修改為2,內(nèi)存中num也被修改為2,但是CPU1和CPU3并不知道num值變了。

原理圖4-緩存一致性1

5.2 MESI

當(dāng)CPU寫數(shù)據(jù)時(shí),如果發(fā)現(xiàn)操作的變量是共享變量,即在其它CPU中也存在該變量的副本,系統(tǒng)會(huì)發(fā)出信號(hào)通知其它CPU將該內(nèi)存變量的緩存行設(shè)置為無效。如下圖所示,CPU1和CPU3 中num=1已經(jīng)失效了。

原理圖5-緩存一致性2

當(dāng)其它CPU讀取這個(gè)變量的時(shí),發(fā)現(xiàn)自己緩存該變量的緩存行是無效的,那么它就會(huì)從內(nèi)存中重新讀取。

如下圖所示,CPU1和CPU3發(fā)現(xiàn)緩存的num值失效了,就重新從內(nèi)存讀取,num值更新為2。

原理圖6-緩存一致性3

5.3 總線嗅探

那其他CPU是怎么知道要將緩存更新為失效的呢?這里是用到了總線嗅探技術(shù)。

每個(gè)CPU不斷嗅探總線上傳播的數(shù)據(jù)來檢查自己緩存值是否過期了,如果處理器發(fā)現(xiàn)自己的緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置為無效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從內(nèi)存中把數(shù)據(jù)讀取到處理器緩存中。

原理圖7-緩存一致性4

5.4 總線風(fēng)暴

總線嗅探技術(shù)有哪些缺點(diǎn)?

由于MESI緩存一致性協(xié)議,需要不斷對(duì)主線進(jìn)行內(nèi)存嗅探,大量的交互會(huì)導(dǎo)致總線帶寬達(dá)到峰值。因此不要濫用volatile,可以用鎖來替代,看場(chǎng)景啦~

六、能演示下volatile為什么不保證原子性嗎?

原子性:一個(gè)操作或一系列操作是不可分割的,要么同時(shí)成功,要么同時(shí)失敗。

「這個(gè)定義和volatile啥關(guān)系呀,完全不能理解呀?Show me the code!」

考慮一下這種場(chǎng)景:

當(dāng)20個(gè)線程同時(shí)給number自增1,執(zhí)行1000次以后,number的值為多少呢?

在單線程的場(chǎng)景,答案是20000,如果是多線程的場(chǎng)景下呢?答案是可能是20000,但很多情況下都是小于20000。

示例代碼:

  1. package com.jackson0714.passjava.threads; 
  2.  
  3. /** 
  4.  演示volatile 不保證原子性 
  5.  * @create: 2020-08-13 09:53 
  6.  */ 
  7.  
  8. public class VolatileAtomicity { 
  9.     public static volatile int number = 0; 
  10.  
  11.     public static void increase() { 
  12.         number++; 
  13.     } 
  14.  
  15.     public static void main(String[] args) { 
  16.  
  17.         for (int i = 0; i < 50; i++) { 
  18.             new Thread(() -> { 
  19.                 for (int j = 0; j < 1000; j++) { 
  20.                     increase(); 
  21.                 } 
  22.             }, String.valueOf(i)).start(); 
  23.         } 
  24.  
  25.         // 當(dāng)所有累加線程都結(jié)束 
  26.         while(Thread.activeCount() > 2) { 
  27.             Thread.yield(); 
  28.         } 
  29.  
  30.         System.out.println(number); 
  31.     } 

執(zhí)行結(jié)果:第一次19144,第二次20000,第三次19378。

volatile第一次執(zhí)行結(jié)果

volatile第二次執(zhí)行結(jié)果

volatile第三次執(zhí)行結(jié)果

我們來分析一下increase()方法,通過反編譯工具javap得到如下匯編代碼:

  1. public static void increase(); 
  2.     Code: 
  3.        0: getstatic     #2                  // Field number:I 
  4.        3: iconst_1 
  5.        4: iadd 
  6.        5: putstatic     #2                  // Field number:I 
  7.        8: return 

number++其實(shí)執(zhí)行了3條指令:

getstatic:拿number的原始值 iadd:進(jìn)行加1操作 putfield:把加1后的值寫回

執(zhí)行了getstatic指令number的值取到操作棧頂時(shí),volatile關(guān)鍵字保證了number的值在此時(shí)是正確的,但是在執(zhí)行iconst_1、iadd這些指令的時(shí)候,其他線程可能已經(jīng)把number的值改變了,而操作棧頂?shù)闹稻妥兂闪诉^期的數(shù)據(jù),所以putstatic指令執(zhí)行后就可能把較小的number值同步回主內(nèi)存之中。

總結(jié)如下:

在執(zhí)行number++這行代碼時(shí),即使使用volatile修飾number變量,在執(zhí)行期間,還是有可能被其他線程修改,沒有保證原子性。

七、怎么保證輸出結(jié)果是20000呢?

7.1 synchronized同步代碼塊

我們可以通過使用synchronized同步代碼塊來保證原子性。從而使結(jié)果等于20000

  1. public synchronized static void increase() { 
  2.    number++; 

synchronized同步代碼塊執(zhí)行結(jié)果

但是使用synchronized太重了,會(huì)造成阻塞,只有一個(gè)線程能進(jìn)入到這個(gè)方法。我們可以使用Java并發(fā)包(JUC)中的AtomicInterger工具包。

7.2 AtomicInterger原子性操作

我們來看看AtomicInterger原子自增的方法getAndIncrement()

AtomicInterger

  1. public static AtomicInteger atomicInteger = new AtomicInteger(); 
  2.  
  3. public static void main(String[] args) { 
  4.  
  5.     for (int i = 0; i < 20; i++) { 
  6.         new Thread(() -> { 
  7.             for (int j = 0; j < 1000; j++) { 
  8.                 atomicInteger.getAndIncrement(); 
  9.             } 
  10.         }, String.valueOf(i)).start(); 
  11.     } 
  12.  
  13.     // 當(dāng)所有累加線程都結(jié)束 
  14.     while(Thread.activeCount() > 2) { 
  15.         Thread.yield(); 
  16.     } 
  17.  
  18.     System.out.println(atomicInteger); 

多次運(yùn)行的結(jié)果都是20000。

getAndIncrement的執(zhí)行結(jié)果

八、禁止指令重排又是啥?

說到指令重排就得知道為什么要重排,有哪幾種重排。

如下圖所示,指令執(zhí)行順序是按照1>2>3>4的順序,經(jīng)過重排后,執(zhí)行順序更新為指令3->4->2->1。

原理圖8-指令重排

會(huì)不會(huì)感覺到重排把指令順序都打亂了,這樣好嗎?

可以回想下小學(xué)時(shí)候的數(shù)學(xué)題:2+3-5=?,如果把運(yùn)算順序改為3-5+2=?,結(jié)果也是一樣的。所以指令重排是要保證單線程下程序結(jié)果不變的情況下做重排。

8.1 為什么要重排

計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。

8.2 有哪幾種重排

1.編譯器優(yōu)化重排:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

2.指令級(jí)的并行重排:現(xiàn)代處理器采用了指令級(jí)并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。

3.內(nèi)存系統(tǒng)的重排:由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

原理圖9-三種重排

注意:

  • 單線程環(huán)境里面確保最終執(zhí)行結(jié)果和代碼順序的結(jié)果一致
  • 處理器在進(jìn)行重排序時(shí),必須要考慮指令之間的數(shù)據(jù)依賴性
  • 多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個(gè)線程中使用的變量能否保證一致性是無法確定的,結(jié)果無法預(yù)測(cè)。

8.3 舉個(gè)例子來說說多線程中的指令重排?

設(shè)想一下這種場(chǎng)景:定義了變量num=0和變量flag=false,線程1調(diào)用初始化函數(shù)init()執(zhí)行后,線程調(diào)用add()方法,當(dāng)另外線程判斷flag=true后,執(zhí)行num+100操作,那么我們預(yù)期的結(jié)果是num會(huì)等于101,但因?yàn)橛兄噶钪嘏诺目赡?,num=1和flag=true執(zhí)行順序可能會(huì)顛倒,以至于num可能等于100

  1. public class VolatileResort { 
  2.     static int num = 0; 
  3.     static boolean flag = false
  4.     public static void init() { 
  5.         num= 1; 
  6.         flag = true
  7.     } 
  8.     public static void add() { 
  9.         if (flag) { 
  10.             num = num + 5; 
  11.             System.out.println("num:" + num); 
  12.         } 
  13.     } 
  14.     public static void main(String[] args) { 
  15.         init(); 
  16.         new Thread(() -> { 
  17.             add(); 
  18.         },"子線程").start(); 
  19.     } 

先看線程1中指令重排:

num= 1;flag = true; 的執(zhí)行順序變?yōu)閒lag=true;num = 1;,如下圖所示的時(shí)序圖

原理圖10-線程1指令重排

如果線程2 num=num+5 在線程1設(shè)置num=1之前執(zhí)行,那么線程2的num變量值為5。如下圖所示的時(shí)序圖。

原理圖11-線程2在num=1之前執(zhí)行

8.4 volatile怎么實(shí)現(xiàn)禁止指令重排?

我們使用volatile定義flag變量:

  1. static volatile boolean flag = false

「如何實(shí)現(xiàn)禁止指令重排:」

原理:在volatile生成的指令序列前后插入內(nèi)存屏障(Memory Barries)來禁止處理器重排序。

「有如下四種內(nèi)存屏障:」

四種內(nèi)存屏障

「volatile寫的場(chǎng)景如何插入內(nèi)存屏障:」

  • 在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障(寫-寫 屏障)。
  • 在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障(寫-讀 屏障)。

原理圖12-volatile寫的場(chǎng)景如何插入內(nèi)存屏障

StoreStore屏障可以保證在volatile寫(flag賦值操作flag=true)之前,其前面的所有普通寫(num的賦值操作num=1) 操作已經(jīng)對(duì)任意處理器可見了,保障所有普通寫在volatile寫之前刷新到主內(nèi)存。

「volatile讀場(chǎng)景如何插入內(nèi)存屏障:」

  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障(讀-讀 屏障)。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障(讀-寫 屏障)。

原理圖13-volatile讀場(chǎng)景如何插入內(nèi)存屏障

LoadStore屏障可以保證其后面的所有普通寫(num的賦值操作num=num+5) 操作必須在volatile讀(if(flag))之后執(zhí)行。

十、volatile常見應(yīng)用

這里舉一個(gè)應(yīng)用,雙重檢測(cè)鎖定的單例模式

  1. package com.jackson0714.passjava.threads; 
  2. /** 
  3.  演示volatile 單例模式應(yīng)用(雙邊檢測(cè)) 
  4.  * @author: 悟空聊架構(gòu) 
  5.  * @create: 2020-08-17 
  6.  */ 
  7.  
  8. class VolatileSingleton { 
  9.     private static VolatileSingleton instance = null
  10.     private VolatileSingleton() { 
  11.         System.out.println(Thread.currentThread().getName() + "\t 我是構(gòu)造方法SingletonDemo"); 
  12.     } 
  13.     public static VolatileSingleton getInstance() { 
  14.         // 第一重檢測(cè) 
  15.         if(instance == null) { 
  16.             // 鎖定代碼塊 
  17.             synchronized (VolatileSingleton.class) { 
  18.                 // 第二重檢測(cè) 
  19.                 if(instance == null) { 
  20.                     // 實(shí)例化對(duì)象 
  21.                     instance = new VolatileSingleton(); 
  22.                 } 
  23.             } 
  24.         } 
  25.         return instance; 
  26.     } 

代碼看起來沒有問題,但是 instance = new VolatileSingleton();其實(shí)可以看作三條偽代碼:

  1. memory = allocate(); // 1、分配對(duì)象內(nèi)存空間 
  2. instance(memory); // 2、初始化對(duì)象 
  3. instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance != null 

步驟2 和 步驟3之間不存在 數(shù)據(jù)依賴關(guān)系,而且無論重排前 還是重排后,程序的執(zhí)行結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。

  1. memory = allocate(); // 1、分配對(duì)象內(nèi)存空間 
  2. instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance != null,但是對(duì)象還沒有初始化完成 
  3. instance(memory); // 2、初始化對(duì)象 

如果另外一個(gè)線程執(zhí)行:if(instance == null)時(shí),則返回剛剛分配的內(nèi)存地址,但是對(duì)象還沒有初始化完成,拿到的instance是個(gè)假的。如下圖所示:

原理圖14-雙重檢鎖存在的并發(fā)問題

解決方案:定義instance為volatile變量

  1. private static volatile VolatileSingleton instance = null

十一、volatile都不保證原子性,為啥我們還要用它?

奇怪的是,volatile都不保證原子性,為啥我們還要用它?

volatile是輕量級(jí)的同步機(jī)制,對(duì)性能的影響比synchronized小。

典型的用法:檢查某個(gè)狀態(tài)標(biāo)記以判斷是否退出循環(huán)。

比如線程試圖通過類似于數(shù)綿羊的傳統(tǒng)方法進(jìn)入休眠狀態(tài),為了使這個(gè)示例能正確執(zhí)行,asleep必須為volatile變量。否則,當(dāng)asleep被另一個(gè)線程修改時(shí),執(zhí)行判斷的線程卻發(fā)現(xiàn)不了。

「那為什么我們不直接用synchorized,lock鎖?它們既可以保證可見性,又可以保證原子性為何不用呢?」

因?yàn)閟ynchorized和lock是排他鎖(悲觀鎖),如果有多個(gè)線程需要訪問這個(gè)變量,將會(huì)發(fā)生競(jìng)爭(zhēng),只有一個(gè)線程可以訪問這個(gè)變量,其他線程被阻塞了,會(huì)影響程序的性能。

注意:當(dāng)且僅當(dāng)滿足以下所有條件時(shí),才應(yīng)該用volatile變量

對(duì)變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個(gè)線程更新變量的值。

該變量不會(huì)與其他的狀態(tài)一起納入不變性條件中。

在訪問變量時(shí)不需要加鎖。

十二、volatile和synchronzied的區(qū)別

  • volatile只能修飾實(shí)例變量和類變量,synchronized可以修飾方法和代碼塊。
  • volatile不保證原子性,而synchronized保證原子性
  • volatile 不會(huì)造成阻塞,而synchronized可能會(huì)造成阻塞
  • volatile 輕量級(jí)鎖,synchronized重量級(jí)鎖
  • volatile 和synchronized都保證了可見性和有序性

十三、小結(jié)

volatile 保證了可見性:當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改。

volatile 保證了單線程下指令不重排:通過插入內(nèi)存屏障保證指令執(zhí)行順序。

volatitle不保證原子性,如a++這種自增操作是有并發(fā)風(fēng)險(xiǎn)的,比如扣減庫(kù)存、發(fā)放優(yōu)惠券的場(chǎng)景。

volatile 類型的64位的long型和double型變量,對(duì)該變量的讀/寫具有原子性。

volatile 可以用在雙重檢鎖的單例模式中,比synchronized性能更好。

volatile 可以用在檢查某個(gè)狀態(tài)標(biāo)記以判斷是否退出循環(huán)。

代碼已提交到github/碼云:https://gitee.com/jayh2018/PassJava-Learning

參考資料:

《深入理解Java虛擬機(jī)》

《Java并發(fā)編程的藝術(shù)》

《Java并發(fā)編程實(shí)戰(zhàn)》

本文轉(zhuǎn)載自微信公眾號(hào)「悟空聊架構(gòu)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系悟空聊架構(gòu)公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 悟空聊架構(gòu)
相關(guān)推薦

2022-05-06 17:34:27

安全代碼軟件漏洞

2024-03-22 11:27:54

電纜管理數(shù)據(jù)中心

2023-08-01 07:31:22

2021-09-06 09:56:10

人工智能AIAI 芯片

2020-11-05 10:50:09

物聯(lián)網(wǎng)數(shù)據(jù)技術(shù)

2021-01-26 16:21:46

邊緣計(jì)算5GIoT

2024-09-18 05:30:00

GPU內(nèi)存人工智能

2018-01-24 06:47:37

物聯(lián)網(wǎng)開源操作系統(tǒng)

2020-04-21 11:03:34

微服務(wù)數(shù)據(jù)工具

2022-11-21 18:02:04

前端測(cè)試

2021-10-26 10:12:04

技術(shù)債務(wù)軟件開發(fā)應(yīng)用程序

2023-04-10 15:41:35

2018-04-24 15:53:52

2016-10-19 14:15:45

2021-09-30 10:19:29

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT

2021-08-30 14:23:41

身份驗(yàn)證隱私管理網(wǎng)絡(luò)安全

2011-12-01 10:55:16

超級(jí)計(jì)算機(jī)高性能計(jì)算Top500

2025-01-13 07:33:47

2013-07-16 09:31:11

2021-09-07 13:25:31

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)