面试官:volatile如何保证可见性的,具体如何实现?

写在开头

在之前的几篇博文中,我们都提到了 volatile 关键字,这个单词中文释义为:不稳定的,易挥发的,在Java中代表变量修饰符,用来修饰会被不同线程访问和修改的变量,对于方法,代码块,方法参数,局部变量以及实例常量,类常量多不能进行修饰。

自JDK1.5之后,官网对volatile进行了语义增强,这让它在Java多线程领域越发重要!因此,我们今天就抽一晚上时间,来学一学这个关键字,首先,我们从标题入手,思考这样的一个问题:

volatile如何保证可见性,具体如何实现的?

带着疑问,我们继续往下阅读!

volatile如何保证可见性

==volatile保证了不同线程对共享变量进行操作时的可见性,即一个线程修改了共享变量的值,共享变量修改后的值对其他线程立即可见。==

我们先通过之前写的一个小案例来感受一下什么是可见性问题:

【代码示例1】

java 复制代码
public class Test {
    //是否停止 变量
    private static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        //启动线程 1,当 stop 为 true,结束循环
        new Thread(() -> {
            System.out.println("线程 1 正在运行...");
            while (!stop) ;
            System.out.println("线程 1 终止");
        }).start();
        //休眠 1 秒
        Thread.sleep(1000);
        //启动线程 2, 设置 stop = true
        new Thread(() -> {
            System.out.println("线程 2 正在运行...");
            stop = true;
            System.out.println("设置 stop 变量为 true.");
        }).start();
    }
}

输出:

java 复制代码
线程 1 正在运行...
线程 2 正在运行...
设置 stop 变量为 true.

原因: 我们会发现,线程1运行起来后,休眠1秒,启动线程2,可即便线程2把stop设置为true了,线程1仍然没有停止,这个就是因为 CPU 缓存导致的可见性导致的问题。线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。

那这个问题怎么解决呢?很好解决!我们排volatile上场可以秒搞定,只需要给stop变量加上volatile修饰符即可!

【代码示例2】

java 复制代码
//给stop变量增加volatile修饰符
private static volatile boolean stop = false;

输出:

java 复制代码
线程 1 正在运行...
线程 2 正在运行...
设置 stop 变量为 true.
线程 1 终止

从结果中看,线程1成功的读取到了线程而设置为true的stop变量值,解决了可见性问题。那volatile到底是什么让变量在多个线程之间保持可见性的呢?请看下图! 如果我们将变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取,具体实现可总结为5步。

  • 1️⃣在生成最低成汇编指令时,对volatile修饰的共享变量写操作增加Lock前缀指令,Lock 前缀的指令会引起 CPU 缓存写回内存;
  • 2️⃣CPU 的缓存回写到内存会导致其他 CPU 缓存了该内存地址的数据无效;
  • 3️⃣volatile 变量通过缓存一致性协议保证每个线程获得最新值;
  • 4️⃣缓存一致性协议保证每个 CPU 通过嗅探在总线上传播的数据来检查自己缓存的值是不是修改;
  • 5️⃣当 CPU 发现自己缓存行对应的内存地址被修改,会将当前 CPU 的缓存行设置成无效状态,重新从内存中把数据读到 CPU 缓存。

总结

其实volatile关键字不仅仅能解决可见性问题,还可以通过禁止编译器、CPU 指令重排序和部分 happens-before 规则,解决有序性问题,我们放在下一篇聊。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注"JavaBuild888",在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

相关推荐
手揽回忆怎么睡4 分钟前
Java集成whisper.cpp
java·开发语言·whisper
无限大67 分钟前
为什么玩游戏需要独立显卡?——GPU与CPU的分工协作
后端·程序员
无名-CODING12 分钟前
栈与队列学习笔记
java·笔记
Hui Baby15 分钟前
LSM 原理、实现及与 B+ 树的核心区别
java·linux·算法
NZT-4815 分钟前
C++基础笔记(二)队列deque,queue和堆priority_queue
java·c++·笔记
Moment18 分钟前
小米不仅造车,还造模型?309B参数全开源,深度思考完胜DeepSeek 🐒🐒🐒
前端·人工智能·后端
Tadas-Gao20 分钟前
存储技术革命:SSD、PCIe与NVMe的创新架构设计与性能优化
java·性能优化·架构·系统架构·存储
codergjw29 分钟前
常见面试题
java
马卡巴卡29 分钟前
SpringBoot集成Spring Statemachine(状态机)实战教程
后端
咕噜企业分发小米29 分钟前
如何平衡服务器内存使用率和系统稳定性?
java·服务器·前端