volatile关键字
先举一个例子,来看一下不使用volatile的时候
1 | public class TestVolatile { |
结果
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的写入对所有处理器可见。开销最大