1.volatile能够保证变量的可见性,但是并不能保证变量的原子性和有序性。
2.它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
3.它会强制将对缓存的修改操作立即写入主存;
4.如果是写操作,它会导致其他CPU中对应的缓存行无效。
System.out.println引发的volatile和synchronize
Java中的双重检查锁(double checked locking)
volatile的两种用法
- 一次性状态标识
- 一种单例模式:双重检查单例模式
一次性状态标识
1 | public class Demo2 { |
运行结果如下:
1 | 开始工作 |
并没有输出”已经停止工作”,说明线程doWork()
并没有感觉到线程shutdown()
状态的变化,从内存角度讲:
每个线程都有自己的工作内存,当线程dowork()
启动时,会把主内存中的shudown=false
拷贝一份到工作内存中,那么此线程一直使用的是副本shutdown=false
,当线程shutdown()
把变量shutdown
更新为true
同时更新到主内存时,线程dowork()
中shutdown
一直还是使用自己工作内存中的副本false
, 所以造成死循环。
增加关键字volatile
1 | volatile boolean shutdown; |
运行结果如下:
1 | 开始工作 |
关键点:
- 状态标志的状态转换是原子操作。例如上面的代码中,对布尔类型进行赋值操作,在Java中是原子性操作
- 只有一次性的状态转换。上面的代码中,状态标志位只是从false转换为true,并没有继续进行从true到false的转换等。这种转换的一次性杜绝了有序性问题的产生。
双重检查单例模式
1 | public class Singleton{ |
instance = new DoubleCheckSingleton(); 这个操作不是原子性的;
这个操作可以划分为:
- 在Heap中开辟地址,进行对象初始化:new DoubleCheckSingleton()
- 将Heap中初始化完成的DoubleCheckSingleton对象地址,指向Thread Stack中的对象引用instance。
加入关键字volatile
。使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。