java volatile關(guān)鍵字作用及使用場景詳解
1. volatile關(guān)鍵字的作用:保證了變量的可見性(visibility)。被volatile關(guān)鍵字修飾的變量,如果值發(fā)生了變更,其他線程立馬可見,避免出現(xiàn)臟讀的現(xiàn)象。如以下代碼片段,isShutDown被置為true后,doWork方法仍有執(zhí)行。如用volatile修飾isShutDown變量,可避免此問題。
public class VolatileTest3 { static class Work { boolean isShutDown = false; void shutdown() { isShutDown = true; System.out.println("shutdown!"); } void doWork() { while (!isShutDown) { System.out.println("doWork"); } } } public static void main(String[] args) { Work work = new Work(); new Thread(work::doWork).start(); new Thread(work::doWork).start(); new Thread(work::doWork).start(); new Thread(work::shutdown).start(); new Thread(work::doWork).start(); new Thread(work::doWork).start(); new Thread(work::doWork).start(); } }
出現(xiàn)臟讀時(shí),運(yùn)行結(jié)果如下:
2. 為什么會(huì)出現(xiàn)臟讀?
Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中,每個(gè)線程都有自己的工作內(nèi)存。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對(duì)主存進(jìn)行操作。并且每個(gè)線程不能訪問其他線程的工作內(nèi)存。變量的值何時(shí)從線程的工作內(nèi)存寫回主存,無法確定。
3. happens-before規(guī)則的理解與勘誤
在網(wǎng)上查volatile關(guān)鍵字相關(guān)信息時(shí),多篇博客提到了happens-before原則,個(gè)人對(duì)此原則的理解是:當(dāng)操作該volatile變量時(shí),所有前序?qū)υ撟兞康牟僮鞫家淹瓿桑ㄈ绮淮嬖谝炎兏?,但未寫回主存的情況),所有后續(xù)對(duì)該變量的操作,都未開始。僅此而已。
這里,我認(rèn)為網(wǎng)上很常見的一個(gè)理論對(duì)此理解有誤,如下圖。此觀點(diǎn)認(rèn)為,由于volatile變量flag的happens-before原則,所以A線程2處對(duì)其的寫操作一定先于B線程3處對(duì)其的讀操作。其實(shí)這種觀點(diǎn)是有邏輯缺陷的,如果存在一個(gè)C線程,先讀取flag的值,后寫入flag的值,那C線程的執(zhí)行時(shí)機(jī)是什么呢?如果還有其他D、E線程呢。。。對(duì)于這段代碼的正確理解是,只要3處拿到的flag是true,那么a的值一定是1,而不是0.因?yàn)関olition修飾的變量,處理器不會(huì)對(duì)其進(jìn)行重排序,所以1處對(duì)a的賦值,一定發(fā)生在2處對(duì)flag的賦值之前。如果flag不是volatile變量,那么1處和2處代碼的執(zhí)行順序是無法保證的(處理器的指令重排序),雖然大部分情況1會(huì)先于2執(zhí)行。happens-before原則約束的并不是多線程對(duì)同一變量的讀和寫操作之間的順序,而是保證讀操作時(shí),前序所有對(duì)該變量的寫操作已生效(寫回主存)。
驗(yàn)證如下:
public class VolatileTest { static class A { int a = 0; volatile boolean flag = false; void writer() { a = 1; //1 flag = true; //2 System.out.println("write"); } void reader() { if (flag) { //3 int i = a; //4 System.out.println("read true"); System.out.println("i is :" + i); } else { int i = a; System.out.println("read false"); System.out.println("i is :" + i); } } } public static void main(String[] args) { A aaa = new A(); new Thread(() -> aaa.reader()).start(); new Thread(() -> aaa.writer()).start(); } }
運(yùn)行結(jié)果如下,在寫操作執(zhí)行之前,讀操作已完成
4. volatile關(guān)鍵字使用場景
注意:volatile只能保證變量的可見性,不能保證對(duì)volatile變量操作的原子性,見如下代碼:
public class VolatileTest2 { static class A { volatile int a = 0; void increase() { a++; } int getA(){ return a; } } public static void main(String[] args) { A a = new A(); new Thread(() -> { for (int i = 0;i < 1000;i++) { a.increase(); } System.out.println(a.getA()); }).start(); new Thread(() -> { for (int i = 0;i < 2000;i++) { a.increase(); } System.out.println(a.getA()); }).start(); new Thread(() -> { for (int i = 0;i < 3000;i++) { a.increase(); } System.out.println(a.getA()); }).start(); new Thread(() -> { for (int i = 0;i < 4000;i++) { a.increase(); } System.out.println(a.getA()); }).start(); new Thread(() -> { for (int i = 0;i < 5000;i++) { a.increase(); } System.out.println(a.getA()); }).start(); } }
運(yùn)行結(jié)果如下,volatile無法保證a++操作的原子性。
volatile正確的使用方法可參考:http://www.dbjr.com.cn/article/166888.htm
以上就是本次介紹知識(shí)點(diǎn)的全部內(nèi)容,感謝大家對(duì)腳本之家的支持。
相關(guān)文章
mybatis框架之mybatis中dao層開發(fā)的兩種方法
這篇文章主要介紹了mybatis框架之mybatis中dao層開發(fā)的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07mybatis plus自動(dòng)生成代碼tinyint(1)自動(dòng)轉(zhuǎn)換為Boolean的問題及解決
這篇文章主要介紹了mybatis plus自動(dòng)生成代碼tinyint(1)自動(dòng)轉(zhuǎn)換為Boolean的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08java使用spring實(shí)現(xiàn)讀寫分離的示例代碼
本篇文章主要介紹了java使用spring實(shí)現(xiàn)讀寫分離的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解
這篇文章主要介紹了Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Mybatis SqlSessionFactory與SqlSession詳細(xì)講解
SqlSessionFactory是MyBatis的核心類之一,其最重要的功能就是提供創(chuàng)建MyBatis的核心接口SqlSession,所以我們需要先創(chuàng)建SqlSessionFactory,為此我們需要提供配置文件和相關(guān)的參數(shù)2022-11-11SpringBoot配置Redis自定義過期時(shí)間操作
這篇文章主要介紹了SpringBoot配置Redis自定義過期時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07基于Springboot2.3訪問本地路徑下靜態(tài)資源的方法(解決報(bào)錯(cuò):Not allowed to load local
這篇文章主要介紹了基于Springboot2.3訪問本地路徑下靜態(tài)資源的方法(解決報(bào)錯(cuò):Not allowed to load local resource),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08