精通Java中的volatile关键字

在一些开源的框架的源码当中时不时均可以看到volatile这个关键字,最近特地学习一下volatile关键字的使用方法。java

不少资料中是这样介绍volatile关键字的:安全

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。

文字不太好理解,经过例子来理解。微信

一、例子

首先看一个没有使用volatile关键字例子:框架

package com.swnote.java;

/**
 * volatile测试例子
 *
 * @author lzj
 * @date [2019-04-47]
 */
public class VolatileTest {

    private boolean flag;

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        test.test();
    }

    public void test() {
        new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
        }).start();

        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println("thread flag = " + flag);
                }
            }
        }).start();
    }
}

该例子中定义了一个flag共享变量,test方法里面开启了两个线程,第一个线程在等待1秒中后修改共享变量flag的值为true,第二个线程经过循环判断flag的值,当flag的值为true时,输出内容。学习

此时有两种猜测:测试

  • 执行后,能够看到输出内容,即说明第二个线程可以感知到第一个线程对共享变量flag的修改
  • 执行后,没有任务内容,即说明第二个线程没法感知到第一个线程对共享变量flag的修改

而后执行结果为:线程

没有任务的输出内容,即证实了此时这样状况下第二个线程没法感知到第一个线程对共享变量flag的修改的code

如今修改一下例子,即为flag变量加上volatile关键字,即:blog

private volatile boolean flag;

而后再运行,此时结果为:内存

此时就有内容输出了,说明加上volatile关键字后,第二个线程能够感知到第一个线程对共享变量flag的修改的,这就是上面概念中所说的volatile在多处理器开发中保证了共享变量的“可见性”。

二、原理

经过上面的例子证实了volatile在多处理器开发中保证了共享变量的“可见性”,那它是怎么实现的呢?

这时就得介绍一下Java的内存模型了,首先看以下示意图:

Java内存模型是如上面所示的:

共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的全部操做都必须在本身的本地内存中进行,而不能直接读写主内存中的变量。

根据此理解,上述例子的在没有加volatile时的状况是这样的:

第一个线程从主内存中获取共享变量flag的值,此时值为false,将该值放到本身的本地内存中,而后对变量进行修改,将值改成true,此时也只是将本地内存中flag的值改成了true,此时尚未将值同步到主内存中,而后第二线程也是将共享变量flag的值放到本身的本地内存中,而此时flag的值仍是为false,因此就是一直没有内容输出了。

然而加上volatile关键字后,第一个线程对flag的修改会强制刷新到主内存中去,同时还会致使其余线程中的本地内存的值会无效,须要从新到主内存获取,这样就保证了第一个线程对flag修改后,第二线程可以感知到。

三、注意点

volatile是轻量级的synchronized,可是它是不可以代替synchronized的,由于volatile只能保证原子性操做的安全,对于复合操做,volatile是不能保证线程安全的。

例如:

package com.swnote.java;

/**
 * 复合操做例子
 *
 * @author lzj
 * @date [2019-04-27]
 */
public class StatisticTest {
    private volatile int num = 0;

    public static void main(String[] args) {
        StatisticTest test = new StatisticTest();
        test.statistic();
    }

    public void statistic() {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                num++;
            }).start();
        }

        System.out.println("num = " + num);
    }
}

指望的运行结果是20,但是几乎每次运行结果都是不同的,例若有的结果为:

这是由于num++这个操做不是原子性的,因此即便使用了volatile关键字,也是不能保证安全的。

关注我

以你最方便的方式关注我:
微信公众号:

相关文章
相关标签/搜索