【java并发编程实战3】解密volatile
in java并发编程实战 with 0 comment

【java并发编程实战3】解密volatile

in java并发编程实战 with 0 comment

自从jdk1.5以后,volatile可谓发生了翻天覆地的变化,从一个一直被吐槽的关键词,变成一个轻量级的线程通信代名词。

接下来我们将从以下几个方面来分析以下volatile

重排序与as if serial的关系

重排序值得是编译器与处理器为了优化程序的性能,而对指令序列进行重新排序的。

但是并不是什么情况下都可以重排序的,

根据 as if serial原则,它强调了单线程。那么多线程发生重排序又是怎么样的呢?

请看下面代码

public class VolatileExample1 {

    /**
     * 共享变量 name
     */
    private static String name = "init";


    /**
     * 共享变量 flag
     */
    private  static boolean flag = false;


    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            name = "yukong";			// 1
            flag = true;				// 2
        });
        Thread threadB = new Thread(() -> {
            if (flag) {   				// 3
                System.out.println("flag = " + flag + " name = " +name);  // 4
            };
        });
    }
}

上面代码中,name输出一定是yukong吗,答案是不一定,根据happen-before原则与as if serial

原则,由于 1、2不存在依赖关系,可以重排序,操作3、操作4也不存在数据依赖,也可以重排序。

那么就有可能发生下面的情况

上图中,操作1与操作2发生了重排序,程序运行的时候,线程A先将flag更改成true,然后线程B读取flag变量并且判断,由于此时flag已经是true,线程B将继续读取name的值,由于此时线程name的值还没有被线程A写入,那么线程此时输出的name就是初始值,因为在多线程的情况下,重排序存在线程安全问题。

volatile的特点

volatile变量具有以下的特点。

这里我先介绍一下volatile关键词的特点,接下来我们将会从它的内存语义来解释,为什么它会具有以上的特点,以及它使用的场景。

volatile的内存语义

如果一个场景存在对volatile变量的读写场景,在读线程B读一个volatile变量后,,写线程A在写这个volatile变量前所有的所见的共享变量的值都将会立即变得对读线程B可见。

那么这种内存语义是怎么实现的呢?

其实编译器生产字节码的时候,会在指令序列中插入内存屏障来禁止指令排序。下面就是JMM内存屏障插入的策略。

那么这些策略中,插入这些屏障有什么作用呢?我们逐条逐条分析一下。

根据这些策略,volatile变量禁止了部分的重排序,这样也是为什么我们会说volatile具有一定的有序的原因。

根据以上分析的volatile的内存语义,大家也就知道了为什么前面我们提到的happen-before原则会有一条

那么根据volatile的内存语义,我们只需要更改之前的部分代码,只能让它正确的执行。

即把flag定义成一个volatile变量即可。

public class VolatileExample1 {

    /**
     * 共享变量 name
     */
    private static String name = "init";


    /**
     * 共享变量 flag
     */
    private  volatile static boolean flag = false;


    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            name = "yukong";			// 1
            flag = true;				// 2
        });
        Thread threadB = new Thread(() -> {
            if (flag) {   				// 3
                System.out.println("flag = " + flag + " name = " +name);  // 4
            };
        });
    }
}

我们来分析一下

volatile的使用场景

下面我们看看double check的使用

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

至于为何需要这么写请参考:

  《Java 中的双重检查(Double-Check)》http://www.iteye.com/topic/652440

最后大家希望关注一下我的个人公众号。
欢迎大家关注一下我的个人公众号。一起交流一起学习,有问必答。