15

我就站在你面前,你却视而不见!

 4 years ago
source link: https://www.tianheyu.top/archives/java-visibility
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在上一篇文章 一男子给对象转账5000元,居然又退还了! 中,我们学习了并发三大特性之一的原子性,并对原子性问题进行分析。

这篇文章我们就一起来了解下可见性:

可见性

首先看下可见性的概念:

可见性就是指 某一个线程修改了共享变量的值时,其他线程能够立即得知这个修改

什么?难道变量被修改了,线程不应该马上读取到的吗?为什么和我认知的不一样呢?

好的,那么接下来让我们带着问题,一起来搞懂可见性问题。

可见性问题

可见性问题的元凶就是 CPU 缓存 ,都怪 CPU 为程序性能优化做的努力,搞出这么多幺蛾子。

关于 CPU 缓存可以阅读: 原来 CPU 为程序性能优化做了这么多

首先在单核 CPU 上,是不存在可见性问题的,因为所有的线程都在一个 CPU 上执行,所有的线程都是操作同一 CPU 缓存,某一个线程修改了共享变量的值,另外的线程也可以马上读取到,因此是可见的。

JbIfQby.png!web

如上图所示, Thread-0Thread-1 都是在一个 CPU 缓存上进行操作,所以 Thread-0 修改了变量 flag 的值后, Thread-1 再去访问变量 flag ,得到的一定是最新的 flag 值。

然而在多核 CPU 上,由于每个 CPU 都有自己的缓存,当多个不同线程运行在不同的 CPU 上时,这些线程操作的 CPU 缓存也是不同的,因此某一个线程对共享变量进行修改时,另外的线程读取到的不一定是最新值,也就不具有可见性了。

nmua6zb.png!web

如上图所示, Thread-0 是在 CPU-0 上的缓存进行操作, Thread-1 是在 CPU-1 上的缓存进行操作,所以 Thread-0 修改了变量 flag 的值后, Thread-1 再去访问变量 flag ,得到的不一定是最新的 flag 值,因此 Thread-0 对共享变量 flag 的修改对 Thread-1 是不可见的。

下面用一个例子来看下可见性问题,创建一个 VisibilityTest 类,实现 Runnable 接口,在 run() 方法中判断 flag 是否为 true ,若为 true 则进行打印操作,主方法中启动一个线程 thread ,主线程等待 0.5 秒后,将 flag 的值设为 true

public class VisibilityDemo {
    private static class VisibilityTest implements Runnable {
        private boolean flag = false;
        @Override
        public void run() {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + ":" + flag);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest visibilityTest = new VisibilityTest();
        Thread thread = new Thread(visibilityTest);
        thread.start();
        // 等待线程启动
        Thread.sleep(500);
        // 更新 flag 为 true
        visibilityTest.flag = true;
        System.out.println(Thread.currentThread().getName() + ":" + visibilityTest.flag);
    }
}

发现输出的结果为: main:true ,和我们想象的不太一样,按道理 thread 应该会持续打印出 Thread-0:true 的,但是并非如此,这也就验证了我们刚才讲的可见性问题。

那么如何解决可见性问题呢?

可以采用同步的方式去解决或者使用 volatile 关键字也可以保证可见性。

关于 volatile 相关原理可以阅读: 你真的了解 volatile 关键字吗?

总结

本文学习了线程安全三大特性之中的可见性,另外 CPU 缓存在提高程序性能的同时也带来了可见性问题,只有我们理解了可见性的原理,才更容易去诊断并发编程中的 BUG。

参考

《Java并发编程实战》

《深入理解Java虚拟机:JVM高级特性与最佳实践》

《实战Java高并发程序设计》

《Java多线程编程核心技术》

Java并发编程实战


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK