关于volatile的探索
2020-12-27 11:11 CeddieCoding 阅读(84) 评论(0) 收藏 举报在阅读SynchronousQueue源码时,发现有这么一段注释:
// Note: item and mode fields don't need to be volatile
// since they are always written before, and read after,
// other volatile/atomic operations.
大意是item和mode这两个属性不需要用volatile修饰,因为他们总在其他的volatile/atomic操作之前/后被写入/读取。
关于volatile的可见性原理在这里就不详细展开说了,大致是因为CPU三级缓存的存在,各个线程在运行时读取或写入变量时,并未实际读取或写入内存中,而是在操作缓存中的数据,导致线程间共享变量状态不一致。此修饰符会在变量被读之前加入load barrier,在被写入之后加入store barrier,以便其他线程可以读取到。
上段注释中的释义也很清晰,即如果在volatile变量写入之前写入,在volatile变量读取之后读取,那么就会“享受”到volatile的便利性,show me the code:
public class TestVolatile { /*volatile int obj = 0;*/ boolean running = false; boolean get() { return running; } void doSetTrue() { running = true; } public static void main(String[] args) throws InterruptedException { TestVolatile instance = new TestVolatile(); instance.producer(); Thread.sleep(100); instance.consumer(); } private void producer() { Thread t = new Thread(() -> { while ( /*this.obj != -1 &&*/ // volatile读 !this.get()) { doNothing(); } System.out.println("Thread 1 finished."); }); t.start(); } private void consumer() { Thread t = new Thread(() -> { this.doSetTrue(); /*this.obj = 1;*/ // volatile写 System.out.println("Thread 2 finished."); }); t.start(); } private void doNothing() { // do nothing } }
被注释掉的两行分别为volatile的读和写。此段代码的执行结果为:
Thread 2 finished.
并且Thread1一直处于循环状态之中,因为在JIT优化下,指令会被重排序,导致Thread1执行的代码变成以下形式:
if(!this.get()) { while(true) { ... } }
在第一次读取缓存中的值为false时,就会进入到while(true)的死循环内。有以下两种方式改善这种情况:
①-Djava.compiler=NONE:
这种方式简单粗暴,直接禁用了JIT的优化,指令不会被重排序
②volatileg关键字:
如果把被注释掉的部分放开,那么执行结果为:
Thread 2 finished.
Thread 1 finished.
综上可知,即便running不是volatile变量,只要其读写次序在volatile变量的前后,那么也会享受到volatile变量带来的便利性,因为volatile已经强制同步、刷新了缓存,并且防止了指令重排序,普通变量在多线程环境下也自然会被正确的读和写。
浙公网安备 33010602011771号