Java 并发编程(三):如何保证共享变量的可见性?

上一篇,咱们谈了谈如何经过同步来保证共享变量的原子性(一个操做或者多个操做要么所有执行而且执行的过程不会被任何因素打断,要么就都不执行),本篇咱们来谈一谈如何保证共享变量的可见性(多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看获得修改的值)。java

咱们使用同步的目的不只是,不但愿某个线程在使用对象状态时,另一个线程在修改状态,这样容易形成混乱;咱们还但愿某个线程修改了对象状态后,其余线程可以看到修改后的状态——这就涉及到了一个新的名词:内存(可省略)可见性。缓存

要了解可见性,咱们得先来了解一下 Java 内存模型。安全

Java 内存模型(Java Memory Model,简称 JMM)描述了 Java 程序中各类变量(线程之间的共享变量)的访问规则,以及在 JVM 中将变量存储到内存→从内存中读取变量的底层细节。ide

要知道,全部的变量都是存储在主内存中的,每一个线程会有本身独立的工做内存,里面保存了该线程使用到的变量副本(主内存中变量的一个拷贝)。见下图。spa

 

 

也就是说,线程 1 对共享变量 chenmo 的修改要想被线程 2 及时看到,必需要通过 2 个步骤:线程

一、把工做内存 1 中更新过的共享变量刷新到主内存中。
二、将主内存中最新的共享变量的值更新到工做内存 2 中。code

那假如共享变量没有及时被其余线程看到的话,会发生什么问题呢?对象

public class Wanger {
    private static boolean chenmo = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!chenmo) {
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        chenmo = true;

    }

}

这段代码的本意是:在主线程中建立子线程,而后启动它,当主线程休眠 500 毫秒后,把共享变量 chenmo 的值修改成 true 的时候,子线程中的 while 循环停下来。但运行这段代码后,程序彷佛进入了死循环,过了 N 个 500 毫秒,也没有要停下来的意思。blog

为何会这样呢?事件

由于主线程对共享变量 chenmo 的修改没有及时通知到子线程(子线程在运行的时候,会将 chenmo 变量的值拷贝一份放在本身的工做内存当中),当主线程更改了 chenmo 变量的值以后,可是还没来得及写入到主存当中,那么子线程此时就不知道主线程对 chenmo 变量的更改,所以还会一直循环下去。

换句话说,就是:普通的共享变量不能保证可见性,由于普通共享变量被修改以后,何时被写入主内存是不肯定的,当其余线程去读取时,此时内存中可能仍是原来的旧值,所以没法保证可见性。

那怎么解决这个问题呢?

使用 volatile 关键字修饰共享变量 chenmo。

由于 volatile 变量被线程访问时,会强迫线程从主内存中重读变量的值,而当变量被线程修改时,又会强迫线程将最近的值刷新到主内存当中。这样的话,线程在任什么时候候总能看到变量的最新值。

咱们来使用 volatile 修饰一下共享变量 chenmo。

private static volatile boolean chenmo = false;

再次运行代码后,程序在一瞬间就结束了,500 毫秒毕竟很短啊。在主线程(main 方法)将 chenmo 修改成 true 后,chenmo 变量的值当即写入到了主内存当中;同时,致使子线程的工做内存中缓存变量 chenmo 的副本失效了;当子线程读取 chenmo 变量时,发现本身的缓存副本无效了,就会去主内存读取最新的值(由 false 变为 true 了),因而 while 循环也就中止了。

也就是说,在某种场景下,咱们可使用 volatile 关键字来安全地共享变量。这种场景之一就是:状态真正独立于程序内地其余内容,好比一个布尔状态标志(从 false 到 true,也能够再转换到 false),用于指示发生了一个重要的一次性事件

至于 volatile 的原理和实现机制,本篇再也不深刻展开了(小编本身没搞懂,尴尬而不失礼貌的笑一笑)。

须要再次强调地是:

volatile 变量能够被看做是一种 “程度较轻的 synchronized”;与 synchronized 相比,volatile 变量运行时地开销比较少,可是它所能实现的功能也仅是 synchronized 的一部分(只能确保可见性,不能确保原子性)。

原子性咱们上一篇已经讨论过了,增量操做(i++)看上去像一个单独操做,但实际上它是一个由“读取-修改-写入”组成的序列操做,所以 volatile 并不能为其提供必须的原子特性。

除了 volatile 和 synchronized,Lock 也可以保证可见性,它能保证同一时刻只有一个线程获取锁而后执行同步代码,而且在释放锁以前会将对变量的修改刷新到主存当中。关于 Lock 的更多细节,咱们后面再进行讨论。

好了,共享变量的可见性就先介绍到这。但愿本篇文章可以对你们有所帮助,谢谢你们的阅读。

0五、最后

谢谢你们的阅读,原创不易,喜欢就点个赞,这将是我最强的写做动力。若是你以为文章对你有所帮助,也蛮有趣的,就关注一下「沉默王二」公众号。

相关文章
相关标签/搜索