Java程式設計師在開發時候要注意,volatile關鍵字修飾引用變量的陷阱

Java程序員在開發時候要注意,volatile關鍵字修飾引用變量的陷阱

鎮樓小姐姐

可獲得兩大新人禮包

36份一線互聯網Java面試電子書

84個Java稀缺面試題視頻


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

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

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

Java代碼

```java

volatile

int [] data;

valatile boolean [] flags;

volatile Person person;

```

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

Java代碼

```java

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");

}

```

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

Java代碼

```

a = 2760, b = 2761

a = 3586, b = 3587

finished

```

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

如何解決這種情況?

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

Java代碼

```

public int[] getValues() {

return new int[]{a, b};

}

```

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

AtomicReference

Java代碼

```

//修改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數組可能結果是沒問題的。

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


分享到:


相關文章: