首先贴一段测试代码:
- 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, 当修改完后输出提示.
注意: 第一个线程启动后主线程睡眠一段时间, 确保第一个线程已经先于线程二获取 CPU 时间片.
这里结果输出是:
Thread 2 finished.
因为无法获知线程 2 对共享变量 running 做出的修改, 然后线程 1 一直处在运行状态.
这里简单说明一下 Java Mememory Model 简称 JMM:
在本例中线程 2 实际上是修改了自己本地内存中的 running 值, 但是并没有刷新到主内存中, 线程 1 也一直在读自己本地内存中的值, 并没有去主内存中重新获取.
为了让例子最终能输出
Thread 1 finished
方法: 很简单, 直接设置变量 running 为 volatile, 以保证其在多线程环境中的内存可见性问题.
本文的重点是对插入的内存屏障进行测试, 所以以上只是开头.
测试 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();
- }
- }
这里同样能使线程 1 结束. 原因在于新建实例的时候对 volatile 变量进行了读写操作
volatile 插入的内存屏障指令如下图:(这里不拉出重排序相关话题)
总结: 在进行 volatile 变量写的时候, StoreStore 确保前面线程 2 修改的普通变量已经被 flush 到主内存中了.
测试 synchronized 关键字对可见性的影响:
为了套用 Happen-Before 规则, 这里直接在 get() 和 doSetTrue() 方法上加 synchronized 也能保证可见性问题. 但这里假设只在 get() 方法上加同步呢? 结果是线程 1 也能获取最新值.
JMM 关于 synchronized 的两条规定:
线程解锁前, 必须把共享变量的最新值刷新到主内存中
线程加锁时, 将清空工作内存中共享变量的值, 从而使用共享变量时需要从主内存中重新读取最新的值
如果只是在 doSetTrue() 方法上加锁, 线程 1 并不会获取最新的值, 原因是: 虽然线程 2 已经将修改过的变量刷新到主存了, 但是 get() 方法并不会去从主存读取最新的值.
以上例子可能不符合 happen-before 规则, 只是探讨结果出现的原因.
如果文章中出现对某处有错误理解的地方, 欢迎大家指出.
参考文章:
The JSR-133 Cookbook for Compiler Writers http://gee.cs.oswego.edu/dl/jmm/cookbook.html
深入理解 Java 内存模型 (程晓明)
来源: https://juejin.im/post/5c6b6c096fb9a049c64467da