Code Ease Code Ease
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档

神秘的鱼仔

你会累是因为你在走上坡路
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档
服务器
  • Java核心基础

    • 基础篇

    • 集合类

    • JVM虚拟机

    • Java并发

      • 产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?
      • 面试官:我问的是Java内存模型,你回答堆栈方法区干嘛?
      • 指令重排序、内存屏障很难?看完这篇你就懂了!
      • Volatile只会用不知道原理?一篇文章带你深究volatile
        • (一)概述
        • (二)volatile保证可见性
        • (三)Volatile保证有序性
        • (四)Volatile不保证原子性
        • (五)Volatile的使用场景
        • (六)Volatile可能会导致的问题
      • 有关synchronized锁的知识点,我用一篇文章总结了
      • 面试被问AQS、ReentrantLock答不出来?这些知识点让我和面试官聊了半小时!
      • 大厂面试题:你知道JUC中的Semaphore、CyclicBarrier、CountDownLatch吗
      • 关于ThreadLocal的九个知识点,看完别再说不懂了!
  • 框架的艺术

  • 分布式与微服务

  • 开发经验大全

  • 版本新特性

  • Java
  • Java核心基础
  • Java并发
CodeEase
2023-09-19
目录

Volatile只会用不知道原理?一篇文章带你深究volatile

作者:鱼仔
博客首页: codeease.top (opens new window)
公众号:Java鱼仔

# (一)概述

要了解并发编程,首先就需要了解并发编程的三大特性:可见性、原子性和有序性。 我们今天要讲的volatile保证了可见性和有序性,但是不保证原子性。接下来会通过几段代码和几张图来强化对volatile的了解。

# (二)volatile保证可见性

在讲JMM的时候,我们写了一段程序,并知道了两个不同线程之间操作数据是不可见的,即线程B修改了主内存中的共享变量后,线程A是不知道的。这就是线程之间的不可见性。

public class Test {
    private static boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("waiting");
                while (!flag){}
                System.out.println("in");
            }
        }).start();

        Thread.sleep(2000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("change flag");
                flag=true;
                System.out.println("change success");
            }
        }).start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这段代码的结果是第二个线程修改flag的值不会被第一个线程见到

4-1.png

现在我们做个小小的改变,给flag加上volatile修饰词

private static volatile boolean flag=false;
1

4-2.png

对volatile原理的理解还是需要借助JMM,我们拿上来第一段代码的执行流程图:

4-3.jpeg

这是未加volatile时的执行过程,最后会停留在第十步,flag会变成true,但是线程A不知道。

加上volatile后,当主内存的flag被改变时,volatile通过cpu的总线嗅探机制,将其他也正在使用该变量的线程的数据失效掉,使得这些线程要重新读取主内存中的值,最后线程A就发现flag的值被改变了。

# (三)Volatile保证有序性

Volatile通过内存屏障禁止指令的重排序,从而保证执行的有序性。具体的内容我在指令重排序和内存屏障的时候讲到了,有兴趣的小伙伴可以看一下。

# (四)Volatile不保证原子性

首先还是拿出一段代码,这段代码很简单,定义一个count,并且用volatile修饰。接着创建十个线程,每个线程循环1000次count++ :

public class VolatileAtomSample {
    private static volatile int count=0;
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        count++;
                    }
                }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

最后无论执行多少次,你会发现最后输出的count绝大多数都是小于10000,现象已经体现出了volatile不能保证原子性,但是为什么呢?

4-4.jpeg

还是通过一个流程图来表示,当线程A执行完count+1后,将值写回到主内存,这个时候由于volatile的可见性,其他工作内存中的count值会被失效掉重新赋值。

可如果线程B刚好执行到第四步呢,线程B工作内存中的count因为volatile变成了1,assign赋值后的count还是等于1,在这里直接少了一次count++。这就是volatile不能保证原子性的原因。

# (五)Volatile的使用场景

通过一个很经典的场景来展示一下volatile的应用,双重校验单例:

public class Singleton {
    private static Singleton Instance;
    private Singleton(){};
    public static Singleton getInstance(){
        if (Instance==null){
            synchronized (Singleton.class){
                Instance=new Singleton();
            }
        }
        return Instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

上面这段代码相信大家肯定很熟悉,单例模式最经典的一段代码,获取实例对象时如果为空就初始化,如果不为空就返回实例,看着没有问题,但是在高并发环境下这段代码是会出问题的。 Instance=new Singleton(); 实例化一个对象时,需要经历三步:

4-5.png

注意,这三步是有可能发生指令重排序的,因此有可能是先申请内存空间,再把对象赋值到内存里,最后实例化对象。第一步->第三步->第二步的方式来执行。

当此时有两个线程A和B同时申请对象的时候,当线程A执行到重排序后的第二步时

4-6.png

线程B执行了if (Instance==null)这行代码,因为此时instance已经赋值到内存里了,所以会直接return Instance; 但是!这个对象并没有被实例化,因此线程B调用该实例时,就报错了。

这个问题的解决办法就是volatile禁止重排序

private static volatile Singleton Instance;
1

# (六)Volatile可能会导致的问题

凡事都讲究一个度,volatile也是。如果一个程序中用了大量的volatile,就有可能会导致总线风暴,所谓总线风暴,就是指当volatile修饰的变量发生改变时,总线嗅探机制机会将其他内存中的这个变量失效掉,如果volatile很多,无效交互会导致总线带宽达到峰值。因此对volatile的使用也需要适度。

上次更新: 2025/04/29, 17:22:06
指令重排序、内存屏障很难?看完这篇你就懂了!
有关synchronized锁的知识点,我用一篇文章总结了

← 指令重排序、内存屏障很难?看完这篇你就懂了! 有关synchronized锁的知识点,我用一篇文章总结了→

最近更新
01
AI大模型部署指南
02-18
02
半个月了,DeepSeek为什么还是服务不可用
02-13
03
Python3.9及3.10安装文档
01-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 备案图标 浙公网安备33021202002405 | 浙ICP备2023040452号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式