# Java里面volatile关键字修饰引用变量的陷阱
如果我现在问你volatile的关键字的作用,你可能会回答对于一个线程修改的变量对其他的线程立即可见。这种说法没多大问题,但是不够严谨。
严谨的回答应该是volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致,但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性。
下面这些数据结构都属于引用类型,即使使用volatile关键字修饰,也不能保证修改后的数据会立即对其他的多个线程保持一致:
```java
volatile int [] data;
valatile boolean [] flags;
volatile Person person;
```
如何证明?看下面的一段代码:
```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");
}
```
上面的代码,有个实体类Data,它有两个字段,分别是a和b,然后在我们的main方法中,我们声明了
一个for循环1万次,在循环体里面我们先声明了一个写入线程,每次给实体类赋值,接着又声明了一个读取线程,当实体不为null的时候,打印如果有不一致的时候,其字段的值。接着同时启动两个线程,并在主线程中分别等待其结束。
在我的mac系统上,运行了第三次的时候出现了不一致:
```
a = 2760, b = 2761
a = 3586, b = 3587
finished
```
原因是对于属性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> 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关键字修饰引用变量。
分享到:
相关推荐
java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java ...
Java并发编程:volatile关键字解析
主要讲述java线程volatile关键字
可见性即用volatile关键字修饰的成员变量表明该变量不存在工作线程的副本,线程每次直接都从主内存中读取,每次读取的都是最新的值,这也就保证了变量对其他线程的可见性。 另外,使用volatile还能确保变量不能被重...
java volatile 关键字 学习
synchronized是阻塞式同步,在线程...这个实际对普通变量没有规定的,而针对volatile修饰的变量给Java虚拟机特殊的约定,线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读,从而保证数据的可见性。
本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,对Java中volatile关键字实现原理感兴趣的朋友一起通过本文学习吧
volatile是java中的关键词之一,这篇文章主要给大家介绍了关于Java中volatile关键字的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
java里的volatile关键字详解
java语言的volatile教程,java语言的volatile关键字到底怎么用
主要为大家解析了java中volatile关键字,经常有人把volatile关键字和synchronized或者lock混淆,本文就为大家好好区分,感兴趣的小伙伴们可以参考一下
volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。这篇文章主要介绍了Java中volatile关键字的作用与用法详解的相关资料,需要的朋友可以参考下
volatile这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。这篇文章主要介绍了Java中的volatile关键字,需要的朋友可以参考下
java里的volatile关键字详解.pdf
volatile是比synchronized关键字更轻量级的同步机制,访问volatile变量时不会执行加锁操作,因此不会使执行线程阻塞。 volatile保证可见性和禁止指令重排序,底层是通过“内存屏障”来实现,但不保证原子性。 写入...
多方面解读Java中的volatile关键字.rar
Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性...
在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候可以万事大吉。 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 ...
一个生动的例子,详解了Volatile关键字对变量的声明在不同编译环境下可能造成不同的结果
深入了解java并发的volatile关键字的底层设计原理.docx