Java并发编程volatile关键字可见性详解

后端 潘老师 5个月前 (11-28) 112 ℃ (0) 扫码查看

本文主要讲解关于Java并发编程中volatile关键字可见性问题的详解相关内容,让我们来一起学习下吧!

volatile关键字在java并发编程中非常重要,但也有很多同学搞不清它的具体作用及其原理,这篇文章主要讲解volatile的其中一个特性——保证可见性,并简单讲解下其中的原理,后期再将其深入的原理,以及禁止指令重排序问题。

代码案例

为了方便大家理解volatile保证可见性问题,我们先看下一个代码案例:

public class VolatileDemo {
    static boolean run  = true;
    public static void main(String[] args) throws InterruptedException {
        // 创建t线程
        new Thread(()->{
            while (run) {
                // do something
                // 这里做输出或不要打印日志,后面解释原因
            }
        },"t").start();
        // 主线程休眠1s
        Thread.sleep(1000);
        // 将run置为false
        run= false;
    }
}

你觉得1s后,主线程将run置为false,t线程while判断会结束运行吗?正常逻辑,我们认为t线程结束循环,但事实是t线程已经陷入死循环,无法结束,而原因就是,主线程对run的修改对于t线程是不可见的,那么问题就来了,为什么不可见?主要原因在于每个线程都有自己独立的工作内存,而成员变量是存放在主内存中的,每个线程在运行时会将成员变量的副本拷贝一份进自己的工作内存,我们一起看下图解。

原因分析

我们来分析一下:

1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。

2. 因为 t 线程要频繁从主内存中读取 run的值,JIT 编译器会将 run的值缓存至自己工作内存中的高速缓存中,减少对主存中 run的访问,提高效率

3. 1 秒之后,main主线程修改了 run的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值

解决方法

使用volatile(易变关键字)修饰线程间共享变量,使之在线程之间可见。

volatile可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。

我们修改代码之后:

static volatile boolean run = true;

再去测试,发现1s后t线程成功结束了。

注意

在上面的代码中do something位置,我们说“这里做输出或不要打印日志”,只要你在里面调用了System.out.println或log.debug等,就会发现,t线程会停止,run变成可见的了,而原因就在于println的源码中有synchronized同步了:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

这也是下面要讲的。

synchronized关键字保证可见性

其实synchronized 关键字也可以保证可见性,我们看下代码:

public class VolatileDemo {
    static boolean run  = true;
    // 锁对象
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {

        // 创建t线程
        new Thread(()->{
            while (true) {
                synchronized (lock) {
                    if(!run) {
                        break;
                    }
                    // do something
                }
            }
        },"t").start();
        // 主线程休眠1s
        Thread.sleep(1000);
        // 将run置为false
        synchronized (lock) {
            run = false;
        }
    }
}

以上代码也可以实现1s后t线程结束运行。

但相比于 synchronized 关键字(重量级锁)对性能影响较大,更推荐使用 volatile 关键字保证可见性。由于使用 volatile 不会引起上下文的切换和调度,所以 volatile 对性能的影响较小,开销较低。

总结

以上就是Java并发编程volatile关键字可见性详解,至于为什么volatile能保证可见性,我们后面有机会再讲,希望本文对你有帮助。欢迎持续关注潘子夜个人博客(www.panziye.com),学习愉快哦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/11918.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】