volatile 详解

1. 前言

  • 在并发编程的过程中,volatile属性非常重要。
  • 首先我们要了解并发编程的三大特性:可见性, 有序性, 原子性
  • 而我们今天的了解的volatile 就牵扯到可见性, 有序性
  • 同时我也会从个人了解的角度给大家分析下,如果有什么不对的地方也希望大家在评论区指正

2. 适合人群

  • 线程的初学者

3. 可见性

大家可以先看一段代码,自己想想到底会有什么执行结果:

private static boolean flag = true;

    public static void test01() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (flag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        Thread.sleep(2000);
        flag = false;
    }

也许有的人会给出答案说,执行2秒后,就会停止。但是事实会是这样吗?? 大家可以手动执行下。

3.1 内存可见性

针对上述的代码,既然已经提出来了结果肯定不会是正常结束的。那为什么会这样呢???

在这里插入图片描述

  • 这里面会牵扯到线程缓存的事情。
  • 当线程执行的时候,会将使用到的值进行拷贝,拷贝到线程的缓存中。所以我们修改值后其实修改的是主内存的值,跟线程的缓存的值无任何关系。所以就算你修改了值while循环也是不会停止的。 所以这个就是线程的可见性。最起码线程跟主线程之前的内存不可见的
  • 所以我们的目的就是为了让"它"可见

3.2 volatile 属性

private static volatile boolean flag1 = true;

    public static void test02() throws InterruptedException {
        Thread t1 = new Thread(() -> {
           while (flag1) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });

        t1.start();
        Thread.sleep(1000);

        flag1 = false;
    }
  • 大家可以运行上述的代码,实际执行的结果跟我们预想的是一致的。
  • 为什么会出现这种情况呢??? 其实我们仔细观察就会发现 控制循环的变量,是被修饰符volatile 进行修饰的。
  • 这意味着当线程用到变量flag1 的时候,都会从主内存中查找,如果子线程修改了值,同样会通过刷新的形式同步到主内存中。所以这就体现了线程的可见性

3.3 volatile 修饰引用类型

    static class A {
        boolean running = true;
        void m() {
            System.out.println("程序开始...");
            while (running) {}
            System.out.println("程序结束...");
        }
    }

    static private volatile A a = new A();

    public static void test03() throws InterruptedException {
        new Thread(a::m, "t1").start();
        Thread.sleep(1000);
        a.running = false;
    }

看如上实例,结果是:实际的结果跟期望的结果不同,这是为什么呢。因为:如果volatile修饰引用类型的话,引用类型本身可见,但是内部属性是不见的

3.4 我们只能用volatile 来修饰吗

    public static void test04() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (flag) {
                try {
                    System.out.println("执行中...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        Thread.sleep(1000);
        flag = false;
    }
  • 通过上述的实例可以得知,其实这么写也是可以的正常执行的。那为什么呢???
  • 其实因为我们添加了System.out.println("执行中...");. 这么神奇吗??? 接下来让我们看下println 源码的实现

在这里插入图片描述
通过上述的截图没看错,锁synchronized 也是设置线线程可见性的

4. 顺序性

你以为程序的执行是顺序的吗??? 不,理解错了不是你以为的就是你以为的。有时候 我们程序执行的时候是乱序的。

4.1 为什么是乱序的

在这里插入图片描述

  • 什么情况下都可以乱序吗???

如果不影响单线程的最终一致性的话,都有可能进行乱序执行。执行的目的还是提高效率

  • volaitile 是如何阻止乱序的

在这里插入图片描述

  • 每个实现jjvm底层的中间件,都必须实现JVM内存屏障
  • volatile 底层就是基于内存屏障来防止乱序的

5. 结束

  • 上述的只是对volatile的使用场景,以及解决问题进行讲解。
  • 但是对乱序并没有做过多解释。第一呢,怕误人子弟,第二呢,感觉不是几个实例就能说清楚的。其实可以理解为就算n++ 这么一句话,翻译成JVM识别的语句,以及底层识别的语句都会转换成很多语句。在多线程的模式下,任何语句执行都会被打断,从而导致乱序执行。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值