一文精通Java中的volatile關(guān)鍵字
前言
在一些開源的框架的源碼當(dāng)中時不時都可以看到volatile這個關(guān)鍵字,最近特意學(xué)習(xí)一下volatile關(guān)鍵字的使用方法。
volatile 關(guān)鍵字:當(dāng)多個線程進(jìn)行操作共享數(shù)據(jù)時,可以保證內(nèi)存中的數(shù)據(jù)可見。 相較于 synchronized 是一種較為輕量級的同步策略。
缺點:
1. volatile 不具備“互斥性”
2. volatile 不能保證變量的“原子性”
很多資料中是這樣介紹volatile關(guān)鍵字的:
volatile是輕量級的synchronized,它在多處理器開發(fā)中保證了共享變量的“可見性”。可見性的意思是當(dāng)一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。
文字不太好理解,通過例子來理解。
1、例子
首先看一個沒有使用volatile關(guān)鍵字例子:
package com.swnote.java; /** * volatile測試?yán)? * * @author lzj * @date [2019-04-47] */ public class VolatileTest { private boolean flag; public static void main(String[] args) { VolatileTest test = new VolatileTest(); test.test(); } public void test() { new Thread(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; }).start(); new Thread(() -> { while (true) { if (flag) { System.out.println("thread flag = " + flag); } } }).start(); } }
該例子中定義了一個flag共享變量,test方法里面開啟了兩個線程,第一個線程在等待1秒中后修改共享變量flag的值為true,第二個線程通過循環(huán)判斷flag的值,當(dāng)flag的值為true時,輸出內(nèi)容。
此時有兩種猜想:
- 執(zhí)行后,可以看到輸出內(nèi)容,即說明第二個線程能夠感知到第一個線程對共享變量flag的修改
- 執(zhí)行后,沒有任務(wù)內(nèi)容,即說明第二個線程無法感知到第一個線程對共享變量flag的修改
然后執(zhí)行結(jié)果為:
沒有任務(wù)的輸出內(nèi)容,即證明了此時這樣情況下第二個線程無法感知到第一個線程對共享變量flag的修改的
現(xiàn)在修改一下例子,即為flag變量加上volatile關(guān)鍵字,即:
private volatile boolean flag;
然后再運(yùn)行,此時結(jié)果為:
此時就有內(nèi)容輸出了,說明加上volatile關(guān)鍵字后,第二個線程可以感知到第一個線程對共享變量flag的修改的,這就是上面概念中所說的volatile在多處理器開發(fā)中保證了共享變量的“可見性”。
2、原理
通過上面的例子證明了volatile在多處理器開發(fā)中保證了共享變量的“可見性”,那它是怎么實現(xiàn)的呢?
這時就得介紹一下Java的內(nèi)存模型了,首先看如下示意圖:
Java內(nèi)存模型是如上面所示的:
共享變量存儲在主內(nèi)存中,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存保存了被該線程使用到的主內(nèi)存的副本拷貝,線程對變量的所有操作都必須在自己的本地內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。
根據(jù)此理解,上述例子的在沒有加volatile時的情況是這樣的:
第一個線程從主內(nèi)存中獲取共享變量flag的值,此時值為false,將該值放到自己的本地內(nèi)存中,然后對變量進(jìn)行修改,將值改為true,此時也只是將本地內(nèi)存中flag的值改為了true,此時還沒有將值同步到主內(nèi)存中,然后第二線程也是將共享變量flag的值放到自己的本地內(nèi)存中,而此時flag的值還是為false,所以就是一直沒有內(nèi)容輸出了。
然而加上volatile關(guān)鍵字后,第一個線程對flag的修改會強(qiáng)制刷新到主內(nèi)存中去,同時還會導(dǎo)致其他線程中的本地內(nèi)存的值會無效,需要重新到主內(nèi)存獲取,這樣就保證了第一個線程對flag修改后,第二線程能夠感知到。
3、注意點
volatile是輕量級的synchronized,但是它是不能夠代替synchronized的,因為volatile只能保證原子性操作的安全,對于復(fù)合操作,volatile是不能保證線程安全的。
例如:
package com.swnote.java; /** * 復(fù)合操作例子 * * @author lzj * @date [2019-04-27] */ public class StatisticTest { private volatile int num = 0; public static void main(String[] args) { StatisticTest test = new StatisticTest(); test.statistic(); } public void statistic() { for (int i = 0; i < 20; i++) { new Thread(() -> { num++; }).start(); } System.out.println("num = " + num); } }
期望的運(yùn)行結(jié)果是20,可是幾乎每次運(yùn)行結(jié)果都是不一樣的,例如有的結(jié)果為:
這是因為num++這個操作不是原子性的,所以即使使用了volatile關(guān)鍵字,也是不能保證安全的。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
多個sheet Excel 數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫的實現(xiàn)方法
這篇文章主要介紹了多個sheet Excel 數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫的實現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03Java實現(xiàn)爬蟲給App提供數(shù)據(jù)(Jsoup 網(wǎng)絡(luò)爬蟲)
這篇文章主要介紹了Java實現(xiàn)爬蟲給App提供數(shù)據(jù),即Jsoup 網(wǎng)絡(luò)爬蟲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-01-01Java數(shù)據(jù)結(jié)構(gòu)之棧的基本定義與實現(xiàn)方法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之棧的基本定義與實現(xiàn)方法,簡單描述了數(shù)據(jù)結(jié)構(gòu)中棧的功能、原理,并結(jié)合java實例形式分析了棧的基本定義與使用方法,需要的朋友可以參考下2017-10-10