Java裡面volatile關鍵字修飾引用變量的陷阱

Java裡面volatile關鍵字修飾引用變量的陷阱


Java裡面volatile關鍵字修飾引用變量的陷阱

如果我現在問你volatile的關鍵字的作用,你可能會回答對於一個線程修改的變量對其他的線程立即可見。這種說法沒多大問題,但是不夠嚴謹。

嚴謹的回答應該是volatile關鍵字對於基本類型的修改可以在隨後對多個線程的讀保持一致,但是對於引用類型如數組,實體bean,僅僅保證引用的可見性,但並不保證引用內容的可見性。

下面這些數據結構都屬於引用類型,即使使用volatile關鍵字修飾,也不能保證修改後的數據會立即對其他的多個線程保持一致:

volatile int [] data;valatile boolean [] flags;volatile Person person;

如何證明?看下面的一段代碼:

 private static volatile Data data; public static void setData(int a, int b) { data = new Data(a, b); } private static class Data { private int a; private int b; public Data(int a, int b) { this.a = a; this.b = b; } public int getA() { return a; } public int getB() { return b; } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10000; i++) { int a = i; int b = i; //writer Thread writerThread = new Thread(() -> {setData(a, b);}); //reader Thread readerThread = new Thread(() -> { while (data == null) {} int x = data.getA(); int y = data.getB(); if (x != y) { System.out.printf("a = %s, b = %s%n", x, y); } }); writerThread.start(); readerThread.start(); writerThread.join(); readerThread.join(); } System.out.println("finished"); }

上面的代碼,有個實體類Data,它有兩個字段,分別是a和b,然後在我們的main方法中,我們聲明瞭 一個for循環1萬次,在循環體裡面我們先聲明瞭一個寫入線程,每次給實體類賦值,接著又聲明瞭一個讀取線程,當實體不為null的時候,打印如果有不一致的時候,其字段的值。接著同時啟動兩個線程,並在主線程中分別等待其結束。

在我的mac系統上,運行了第三次的時候出現了不一致:

a = 2760, b = 2761a = 3586, b = 3587finished

原因是對於屬性a和b我們都是分別的讀取,所以缺乏了happens-before關係的約束。

如何解決這種情況?

(1)去掉獨立的getA和getB方法,使用int數組,一次返回兩個屬性

 public int[] getValues() { return new int[]{a, b}; }

(2)使用java併發包下面的基於CAS的原子結構: AtomicReference

//修改1 private static AtomicReference data = new AtomicReference<>();//修改2 public static void setData(int a, int b) { data.compareAndSet(null, new Data(a, b)); } //修改3 Thread readerThread = new Thread(() -> { while (data.get() == null) {} int x = data.get().getA(); int y = data.get().getB(); if (x != y) { System.out.printf("a = %s, b = %s%n", x, y); } });

總結:

本篇文章主要講述了關於volatile修飾引用變量的問題即它只能保證引用本身的可見性,並不能保證內部字段的可見性,如果想要保證內部字段的可見性最好使用CAS的數據結構,這裡還需要說明的的一點是volatile有時候修飾引用類型如boolean數組可能結果是沒問題的,大家可以看我在Stack Overflow上提問的一個問題:

https://stackoverflow.com/questions/50967448/about-java-volatile-array

在編程的世界裡面,對於不確定的事情,我們始終都要以最壞的打算來看待,所以請記住:儘量避免使用volatile關鍵字修飾引用變量。


分享到:


相關文章: