volatile的三个特点保证线程之间的可见性 禁止指令重排 不保证原子性
可见性概念可见性是多线程场景中才讨论的,它表示多线程环境中,当一个线程修改了共享变量的值,其他线程能够知道这个修改。
为什么需要可见性缓存一致性问题: public class Test {
public static void main(String[] args) {
Mythread mythread = new Mythread();
new Thread(() -> {
try {
//延时2s,确保进入while循环
TimeUnit.SECONDS.sleep(2);
//num自增
mythread.increment();
System.out.println("Thread-" + Thread.currentThread().getName() +
" current num value:" + mythread.num);
} catch (Exception e) {
e.printStackTrace();
}
}, "test").start();
while(mythread.num == 0){
//dead
}
System.out.println("game over!!!");
}
}
class Mythread{
//不加volatile,主线程无法得知num的值发生了改变,从而陷入死循环
volatile int num = 0;
public void increment(){
++num;
}
} 如上述代码,如果不加volatile,程序运行结果如下 
加上volatile关键字后,程序运行结果如下 
解决方向:
禁止指令重排指令重排概念编译器和CPU在保证最终结果不变的情况下,对指令的执行顺序进行重排序。
指令重排的问题可以与双重检验实现单例模式联系起来看: 首先,一个对象的创建过程可大致分为以下三步: 分配内存空间 执行对象构造方法,初始化对象 引用指向实例对象在堆中的地址
但是在实际执行过程中,CPU可能会对上述步骤进行优化,进行指令重排 序1->3->2,从而导致引用指向了未初始化的对象,如果这个时候另外一个线 程引用了该未初始化的对象(只执行了1->3两步),就会产生异常。
不保证原子性为什么无法保证具体例子public class Test {
public static void main(String[] args) {
Mythread mythread = new Mythread();
for(int i = 0; i < 6666; ++i){
new Thread(() -> {
try {
mythread.increment();
} catch (Exception e) {
e.printStackTrace();
}
}, "test").start();
}
System.out.println("Thread-" + Thread.currentThread().getName() +
" current num value:" + mythread.num);
}
}
class Mythread{
volatile int num = 0;
public void increment(){
++num;
}
} 上述代码的运行结果如下图 
可以看到,循环执行了6666次,但最后的结果为6663,说明在程序运行过程中出 现了重复的情况。 解决方案使用JUC中的Atomic 类(之后会专门写一篇学习笔记进行阐述) 使用synchronized关键字修饰(不推荐)
volatile保证可见性和解决指令重排的底层原理内存屏障(内存栅栏)组成内存屏障分为两种:Load Barrier 读屏障 和 Store Barrier 写屏障 4种类型屏障种类 | 例子 | 作用 |
---|
LoadLoad屏障 | Load1; LoadLoad; Load2 | 保证Load1读取操作读取完毕后再去执行Load2后续读取操作 | LoadStore屏障 | Load1; LoadStore; Store2 | 保证Load1读取操作读取完毕后再去执行Load2后续写入操作 | StoreStore屏障 | Store1; StoreStore; Store2 | 保证Load1的写入对所有处理器可见后再去执行Load2后续写入操作 | StoreLoad屏障 | Store1; StoreLoad; Load2 | 保证Load1的写入对所有处理器可见后再去执行Load2后续读取操作 |
作用保证特定操作的执行顺序 在每个volatile修饰的全局变量读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障
保证某些变量的内存可见性 在每个volatile修饰的全局变量写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障
|