volatile的正确使用姿势

volatile特性

volatile具备并发三大特性当中的两种:

  • 可见性

​ 简单地说就是volatile变量修改后,所有线程都能立即实时地看到它的最新值。

  • 有序性

​ 有序性是指系统在进行代码优化时,不能把在volatile变量操作后面的语句放到其前面执行,也不能将volatile变量操作前面的语句放在其后执行。

​ 那接下来我们先来看下volatile关键字是如何解决多线程可见性问题的。

volatile可见性

下面的两个例子演示了变量使用volatile和未使用volatile时,变量更新对多线程执行的影响。

//未使用了volatile
public class NonVolatileDemo {
    public static boolean stop = false;//任务是否停止,普通变量

    public static void main(String[] args) throws Exception {
         Thread thread1 = new Thread(() -> {
            while (!stop) { //stop=false,不满足停止条件,继续执行
                //do someting
            }
            System.out.println("stop=true,满足停止条件。" +
                    "停止时间:" + System.currentTimeMillis());
        });
        thread1.start();

        Thread.sleep(100);//保证主线程修改stop=true,在子线程启动后执行。
        stop = true; //true
        System.out.println("主线程设置停止标识 stop=true。" +
                "设置时间:" + System.currentTimeMillis());
    }
}

NonVolatileDemo中,停止标识stop未使用volatile关键字修饰,初始值为false。

  • 创建子线程thread1并启动,在子线程thread1任务中使用停滞标识stop作为判断条件:
  • 当不满足停止条件时,线程会一直运行;
  • 当满足停止条件,终止任务。
  • 稍后,我们在主线程中设置停止标识为true。

我们希望在设置stop=true之后,子线程能够获得到判断条件的变化而停下来

但是执行代码,结果如下图,我们可以看到在主线程设置stop=true后,子线程未及时感知到stop的变化,还在继续执行任务。

也就是子线程并没有知道stop的值改变的这件事情,why???来看看图解:

大家如果对于read/use等指令是干什么的不了解的也不用担心,这是JVM的原子性指令,跟volatile如何实现有序性的内存屏障相关,我们会在下一篇文章中详细讲解。

那通过上图大家知道了一个普通变量在多线程环境下的运行机制,所以为什么出现这个现象也就清楚了,那我们怎样能够解决这个问题呢?使用volatile修饰我们的变量,让它对于所有线程都具有可见性,来看下面的代码

//使用了volatile
public class VolatileDemo {
    public static volatile boolean stop = false;//任务是否停止,volatile变量
    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(() -> {
            while (!stop) { //stop=false,不满足停止条件,继续执行
                //do someting
            }
            System.out.println("stop=true,满足停止条件。" +
                    "停止时间:" + System.currentTimeMillis());
        });
        thread1.start();

        Thread.sleep(100);//保证主线程修改stop=true,在子线程启动后执行。
        stop = true; //true
        System.out.println("主线程设置停止标识 stop=true。" +
                "设置时间:" + System.currentTimeMillis());
    }
}

​ 在VolatileDemo中,停止标识stop使用volatile关键字修饰,初始值为false。其他代码和NonVolatileDemo完全一致。

结果出来了,NonVolatileDemo代码就存在可见性问题;而在VolatileDemo中通过使用volatile关键字,很简单的保证了多线程下共享变量的可见性。

​ 好奇volatile怎么实现的吗?来来来一张图弄懂它!

现在是不是一目了然了呢?不过大家要注意一点,为了让大家好理解我引入了工作内存的概念,但是实际上工作内存不是真实存在的,想要了解真实的实现逻辑需要了解更多偏硬件的知识,大家有兴趣了解可以关注公众号,回复关键字"工作内存”来领取相关资料。

​而且可见性的实现还有更深层的原理,需要配合有序性和内存屏障才能讲透彻,所以有兴趣的同学记得关注下一篇原理剖析哦

初步了解了volatile的基本使用及其可见性,下面我们来看看volatile和synchronized的区别

先上段代码,使用synchonized实现VolatileDemo功能,来直观感受下。

public class SychronizedDemo {
    public static boolean stop = false;//任务是否停止

    //同步静态方法,设置stop
    public static synchronized void setStop(boolean flag) {
        SychronizedDemo.stop = flag;
    }
    //同步静态方法,获取stop
    public static synchronized boolean isStop() {
        return stop;
    }

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(() -> {
            while (!isStop()) { //stop=false,不满足停止条件,继续执行
                //do someting
            }
            System.out.println("stop=true,满足停止条件。" +
                    "停止时间:" + System.currentTimeMillis());
        });
        thread1.start();

        Thread.sleep(100);//保证主线程修改stop=true,在子线程启动后执行。
        setStop(true); //true
        System.out.println("主线程设置停止标识 stop=true。" +
                "设置时间:" + System.currentTimeMillis());
    }
}

​ 在SychronizedDemo中,停止标识stop为普通静态变量,初始值为false。stop的设置方法setStop或获取方法isStop都为同步方法,可以保证锁对象SychronizedDemo类静态变量stop的可见性。执行代码,结果如下图,我们可以看到在主线程设置stop=true后,子线程同时感知到stop的变化终止了任务。

大家可以看到volatile和synchronized都实现了同样的功能,但底层实现是有一定差异的。

volatile关键字是Java提供的最轻量级的同步机制,为字段的访问提供了一种免锁机制,使用它不会引起线程的切换及调度。这时使用volatile要比synchronized要简单有效的多,如果使用synchronized还会影响系统的吞吐量。

那volatile关键字即可以保证可见性,而且使用起来这么方便,那它是解决可见性的万能药吗?我能用volatile代替synchonized吗?

事实上,volatile关键字并不是万能的,因为我们前文讲到了volatile并不能保证原子性。

volatile的正确使用方式

​使用 volatile 变量的主要原因是单个字段同步操作的简易性。如果只使用了volatile就能实现线程安全,那就放心的使用它,如果同时还需要添加其他的同步措施,那就不要使用。

​正确使用的场景举例:变量本身标识是一种状态,或者引用变量的某些属性状态,在代码中需要确保这些状态的可见性,这时就可使用volatile。volatile 变量仅仅是一个状态标识,用于指示发生了一个重要的一次性事件,例如完成初始化标识或请求终止标识。

volatile boolean stop=false;//volatile 变量,用于停止请求的状态标识
public void shutdown() {//停止请求
    stop = true;   
}  
public void doWork() {
    while (!stop) {//判断是否需要停止
        // do Something  
    }  
}
这样只要任何一个线程调用了shutdown(),其他线程在执行doWork时都可以立即感知到stop变量的变化,这时就可以大胆的使用volatile。这种类型的状态标记的一个公共特性是:通常只有一种状态转换,如标志从false 转换为true。

通过本篇文章主要给大家讲解了volatile的可见性,volatile、synchronized区别的初体验,以及volatile的使用建议。

おすすめ

転載: blog.csdn.net/u010074988/article/details/118025349