在本文裏我們給大家分享的是關於java volatile關鍵字作用及使用場景的相關知識點內容,需要的朋友們學習下。
1. volatile關鍵字的作用:保證了變量的可見性(visibility)。被volatile關鍵字修飾的變量,如果值發生了變更,其他線程立馬可見,避免出現髒讀的現象。如以下代碼片段,isShutDown被置爲true後,doWork方法仍有執行。如用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(); } }
出現髒讀時,運行結果如下:
2. 爲什麼會出現髒讀?
Java內存模型規定所有的變量都是存在主存當中,每個線程都有自己的工作內存。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。變量的值何時從線程的工作內存寫回主存,無法確定。
3. happens-before規則的理解與勘誤
在網上查volatile關鍵字相關信息時,多篇博客提到了happens-before原則,個人對此原則的理解是:當操作該volatile變量時,所有前序對該變量的操作都已完成(如不存在已變更,但未寫回主存的情況),所有後續對該變量的操作,都未開始。僅此而已。
這裏,我認爲網上很常見的一個理論對此理解有誤,如下圖。此觀點認爲,由於volatile變量flag的happens-before原則,所以A線程2處對其的寫操作一定先於B線程3處對其的讀操作。其實這種觀點是有邏輯缺陷的,如果存在一個C線程,先讀取flag的值,後寫入flag的值,那C線程的執行時機是什麼呢?如果還有其他D、E線程呢。。。對於這段代碼的正確理解是,只要3處拿到的flag是true,那麼a的值一定是1,而不是0.因爲volition修飾的變量,處理器不會對其進行重排序,所以1處對a的賦值,一定發生在2處對flag的賦值之前。如果flag不是volatile變量,那麼1處和2處代碼的執行順序是無法保證的(處理器的指令重排序),雖然大部分情況1會先於2執行。happens-before原則約束的並不是多線程對同一變量的讀和寫操作之間的順序,而是保證讀操作時,前序所有對該變量的寫操作已生效(寫回主存)。
驗證如下:
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(); } }
運行結果如下,在寫操作執行之前,讀操作已完成
4. volatile關鍵字使用場景
注意:volatile只能保證變量的可見性,不能保證對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(); } }
運行結果如下,volatile無法保證a++操作的原子性。
volatile正確的使用方法可參考:https://www.jb51.net/article/166888.htm
以上就是本次介紹知識點的全部內容,感謝大家對神馬文庫的支持。