public class TestMemoryBarrier { boolean running = false; boolean get() { return running; } void doSetTrue() { running = true; } public static void main(String[] args) throws InterruptedException { TestMemoryBarrier instance = new TestMemoryBarrier(); new Thread( () -> { while (!instance.get()) { } System.out.println("Thread 1 finished."); }).start(); Thread.sleep(100); new Thread( () -> { instance.doSetTrue(); System.out.println("Thread 2 finished."); }).start(); } } 复制代码
首先启动第一个线程线程1去循环获取bool变量 running
的值(默认是 false
),如果 running
变为 true
后循环结束,线程终止。
再起第二个线程线程2去修改 running
的值为 true
,当修改完后输出提示。
这里结果输出是:
Thread 2 finished. 复制代码
因为无法获知线程2对共享变量 running
做出的修改, 然后线程1一直处在运行状态。
这里简单说明一下Java Mememory Model简称JMM:
在本例中线程2实际上是修改了自己本地内存中的 running
值, 但是并没有刷新到主内存中,线程1也一直在读自己本地内存中的值,并没有去主内存中重新获取。
为了让例子最终能输出
Thread 1 finished 复制代码
方法:很简单, 直接设置变量 running
为volatile,以保证其在多线程环境中的内存可见性问题。
本文的重点是对插入的内存屏障进行测试,所以以上只是开头。
添加一个类,包含一个volatile的变量并 赋值 。
在 get()
方法return前, new一个这个新增类的实例对象,完整代码:
class VolatileClass { private volatile int a = 1; } public class TestMemoryBarrier { boolean running = false; boolean get() { VolatileClass aClass = new VolatileClass(); return running; } void doSetTrue() { running = true; } public static void main(String[] args) throws InterruptedException { TestMemoryBarrier instance = new TestMemoryBarrier(); new Thread( () -> { while (!instance.get()) { } System.out.println("Thread 1 finished."); }).start(); Thread.sleep(100); new Thread( () -> { instance.doSetTrue(); System.out.println("Thread 2 finished."); }).start(); } } 复制代码
volatile插入的内存屏障指令如下图:(这里不拉出重排序相关话题)
总结:在进行volatile变量写的时候,StoreStore确保前面线程2修改的普通变量已经被flush到主内存中了。
为了套用Happen-Before规则,这里直接在 get()
和 doSetTrue()
方法上加 synchronized
也能保证可见性问题。但这里假设只在 get()
方法上加同步呢? 结果是线程1也能获取最新值。
JMM关于synchronized的两条规定:
如果只是在 doSetTrue()
方法上加锁,线程1并不会获取最新的值,原因是:虽然线程2已经将修改过的变量刷新到主存了,但是 get()
方法并不会去从主存读取最新的值。