记一次对Java多线程内存可见性的测试

首先贴一段测试代码:

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后循环结束,线程终止。html

再起第二个线程线程2去修改running的值为true,当修改完后输出提示。bash

注意: 第一个线程启动后主线程睡眠一段时间,确保第一个线程已经先于线程二获取cpu时间片。

这里结果输出是:多线程

Thread 2 finished.
复制代码

由于没法获知线程2对共享变量running作出的修改, 而后线程1一直处在运行状态。app

这里简单说明一下Java Mememory Model简称JMM:测试

在本例中线程2其实是修改了本身本地内存中的running值, 可是并无刷新到主内存中,线程1也一直在读本身本地内存中的值,并无去主内存中从新获取。ui

为了让例子最终能输出spa

Thread 1 finished
复制代码

方法:很简单, 直接设置变量running为volatile,以保证其在多线程环境中的内存可见性问题。线程

本文的重点是对插入的内存屏障进行测试,因此以上只是开头。3d

测试volatile插入的内存屏障指令,变动代码为:

添加一个类,包含一个volatile的变量并赋值code

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的两条规定:

  1. 线程解锁前,必须把共享变量的最新值刷新到主内存中
  2. 线程加锁时,将清空工做内存中共享变量的值,从而使用共享变量时须要从主内存中从新读取最新的值

若是只是在doSetTrue()方法上加锁,线程1并不会获取最新的值,缘由是:虽然线程2已经将修改过的变量刷新到主存了,可是get()方法并不会去从主存读取最新的值。

以上例子可能不符合happen-before规则,只是探讨结果出现的缘由。

若是文章中出现对某处有错误理解的地方,欢迎你们指出。

参考文章:

相关文章
相关标签/搜索