0%

volatile关键字

volatile关键字

先举一个例子,来看一下不使用volatile的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class TestVolatile {

public static void main(String[] args) {
ThreadDemo runnable = new ThreadDemo();
new Thread(runnable).start();

while (true){
// 不会停止 一直在while循环
if(runnable.isFlag()){
System.out.println("----------");
break;
}
}
}

}

class ThreadDemo implements Runnable{

private boolean flag;
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;

System.out.println(Thread.currentThread().getName()+"---flag:"+flag);
}

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}

结果

1
Thread-0---flag:true

在这里主线程一直获取不到子线程中flag的状态修改为true了,一直在while循环中不出来,这是为什么呢

为了提高性能,对于共享数据,在每个线程中都会缓存一份数据,

对于上面的例子来说就是 main线程在线程内部缓存了一份flag=false Thread-0线程也在线程内部缓存了一份flag=false,然后Thread-0线程将flag改为true之后同步到主存中,但是main线程的while循环使用的底层的循环机制,效率特别高,根本就没有从主存中重新去获取一下新的数据,线程内的缓存数据没有更新,导致一直在while循环中。

而这种问题就是内存不可见导致的,可以使用synchronized来解决,

synchronized在获取锁前后也要保证数据的一致性,获取锁 读内存屏障 释放锁 写内存屏障

所以可以在while循环中加上synchronized关键字来解决

但是加锁效率太低,所以一般情况下使用volatile关键字解决该问题,对于flag关键字加上volatile来修饰

1
private volatile boolean flag;

所以volatile具有内存可见性的作用

volatile的作用

  • 内存可见性

  • 禁止指令重排序(CPU的缓存一致性协议)

在单例懒汉式中双重检测时就使用了volatile禁止指令重排序

在实例化对象时,JVM分为三步

1、申请内存

2、成员变量初始化

3、赋值给对象

volatile的执行过程

在写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存。

当读一个volatile变量时,JMM会把线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量

内存屏障

内存屏障(Memory Barrier)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。java编译器也会根据内存屏障的规则禁止重排序

分为几个类型

  • LoadLoad屏障:对于Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕

  • StoreStore屏障:对于Store1;StoreStore;Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见

  • LoadStore屏障:对于Load1;LoadStore;Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕

  • StoreLoad屏障:对于Store1;StoreLoad;Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。开销最大