Java volatile关键字:你真的懂了吗?

简介: `volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。

一、volatile概念

volatile 关键字是 Java 语言中的一个轻量级的同步机制,它可以保证共享变量的可见性和有序性,但不能保证原子性。


添加图片注释,不超过 140 字(可选)


二、volatile作用

1. 可见性


添加图片注释,不超过 140 字(可选)


在 Java 中,每个线程都有自己的工作内存(缓存),用于存放它用到的变量的副本。当线程对变量进行修改时,它实际上是在自己的工作内存中对这个变量的副本进行操作,而不是直接操作主内存中的变量。只有在某个时刻,工作内存的变化才会同步到主内存中。这就导致了一个问题:当多个线程同时操作一个变量时,可能会出现变量的值在不同线程间不一致的情况。

volatile 关键字就是用来解决这个问题的。当一个变量被声明为 volatile 时,所有对这个变量的写操作都会直接同步到主内存中,所有对这个变量的读操作都会直接从主内存中读取,从而保证了变量值的一致性。

2. 防止指令重排

为了提高执行效率,编译器和处理器可能会对代码进行重排列,即调整指令的执行顺序。这可能会导致程序的执行结果与预期不符。volatile 关键字可以防止对其修饰的变量相关的操作被重排列,从而保证程序的正确性。

在底层实现上,volatile 的原理依赖于处理器提供的内存屏障(Memory Barrier)。内存屏障是一种 CPU 指令,它的作用是防止指定的内存操作被重排列。当一个变量被声明为 volatile 时,JVM 会在生成的字节码中插入内存屏障指令,以确保对这个变量的读写操作符合 volatile 的语义。


添加图片注释,不超过 140 字(可选)


可以看到

 ILOAD 1  // 将局部变量表中索引为1的int值压入操作数栈,即变量i

 ICONST_1  // 将int类型的常量1压入操作数栈

 IADD  //将栈顶的两个int值相加,并将结果压入操作数栈

 ISTORE 1  // 将栈顶的int值存入局部变量表中索引为1的位置,即变量i

可以看到一行代码变成指令后会成为多行,那么在执行多行指令CPU会存在重排的情况。


三、volatile分解

下面是一个使用volatile关键字演示可见性的Java示例,以及对其进行分析:

```java public class VolatileDemo {     // 定义一个volatile变量     private static volatile boolean flag = false;     public static void main(String[] args) {         // 创建一个线程,修改flag的值         new Thread(new Runnable() {             @Override             public void run() {                 try {                     // 等待3秒                     Thread.sleep(3000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 // 修改flag的值为true                 flag = true;                 System.out.println("flag已经被修改为true");             }         }).start();         // 创建另一个线程,读取flag的值         new Thread(new Runnable() {             @Override             public void run() {                 // 循环检测flag的值                 while (true) {                     //这里不增加任何代码,包括打印语句也不能增加                     if (flag) {                         // 如果flag为true,打印信息并退出循环                         System.out.println("检测到flag为true");                         break;                     }                 }             }         }).start();     } }

运行这个程序,你会看到输出如下:


添加图片注释,不超过 140 字(可选)


这说明第二个线程能够及时感知到第一个线程对flag的修改,这就是volatile变量的可见性。如果去掉volatile关键字,那么第二个线程可能会一直循环,因为它无法看到flag的变化。


添加图片注释,不超过 140 字(可选)


可以看到程序一直没有结束,在循环判断flag的值,一直没有读取到为true值。

要看到效果有个地方需要注意:


添加图片注释,不超过 140 字(可选)



四、应用场景

volatile 关键字在 Java 中是一种轻量级的同步机制,主要用于解决变量在多线程环境下的可见性问题,以及防止指令重排。以下是一些 volatile 的典型应用场景:

  1. 状态标记: 当你需要一个线程去检测某个状态的变化,并根据状态的变化做出相应的动作时,可以使用 volatile 关键字来声明这个状态变量。这样一来,当状态变量发生改变时,其他线程可以立即感知到这个变化,并作出相应的反应。

public class ShutdownHook extends Thread {     private volatile boolean shutdownRequested = false;     public void run() {         while (!shutdownRequested) {             // 业务逻辑         }     }     public void shutdown() {         shutdownRequested = true;     } }

  1. 单例模式的双重检查锁定(Double-Checked Locking):volatile 关键字可以用于实现线程安全的单例模式,其中使用双重检查锁定机制。

public class Singleton {     private static volatile Singleton instance;     public static Singleton getInstance() {         if (instance == null) {             synchronized (Singleton.class) {                 if (instance == null) {                     instance = new Singleton();                 }             }         }         return instance;     } }

  1. 安全的发布对象: 使用 volatile 关键字可以确保对象被安全的发布,即当一个线程初始化一个对象的引用并将其赋值给 volatile 变量时,其他线程可以正确地看到这个被初始化完成的对象。

public class SafePublish {     private volatile Object obj;     public void init() {         obj = new Object();     }     public Object getObj() {         return obj;     } }

这些是 volatile 关键字的一些典型应用场景,但并不局限于这些。在实际的开发中,根据具体的需求和场景,volatile 可以发挥其特性,以达到线程安全的目的。


五、相关面试题

  • volatile关键字的作用是什么?

volatile关键字的作用是保证变量的可见性和禁止指令重排序。当一个变量被声明为 volatile,任何对它的写操作都将立即反映到其他线程中,以确保多线程环境下的数据一致性。

  • volatile关键字能保证哪些特性?

volatile关键字能保证变量的可见性,即一个线程修改了共享变量,其他线程可以立即看到修改后的值。volatile关键字还能禁止指令重排序,确保程序执行的正确性。

  • volatile关键字不能保证哪些特性?

volatile关键字不能保证原子性,因此不能用于需要原子操作的场景。volatile关键字也不能保证顺序性,因此不能用于需要顺序执行的场景。

  • volatile关键字的底层实现原理是什么?

volatile关键字的底层实现原理是通过内存屏障来实现的。内存屏障是一种CPU指令,它的作用是禁止指令重排序,保证特定操作的执行顺序。

  • volatile与synchronized的区别?

volatile和synchronized都是Java语言提供的同步机制,但它们之间存在一些重要区别。

特性

volatile

synchronized

作用域

变量

变量、方法、对象

可见性

保证

保证

原子性

不保证

保证

适用场景

保证共享变量的可见性和禁止指令重排序

保证共享变量的原子性和可见性

volatile 和 synchronized 都用于确保多线程环境下的数据一致性,但它们有几个重要的区别。volatile 用于修饰变量,保证可见性,但不提供互斥性。synchronized 用于修饰代码块或方法,提供互斥性,同时也保证可见性。另外,synchronized 会引入性能开销,而 volatile 不会。

  • 什么是可见性问题,volatile 如何解决它?

可见性问题是指一个线程对共享变量的修改可能不被其他线程立即感知。volatile 通过强制线程从主内存中读取变量的值,而不是从线程的本地缓存中,来解决可见性问题。这确保了一个线程对 volatile 变量的修改对其他线程是可见的。

  • 什么情况下应该使用 volatile?

volatile 主要用于标记变量,当变量被多个线程共享并且一个线程修改了这个变量的值后需要立即通知其他线程,以确保可见性和避免指令重排序时,应该使用 volatile。典型的使用场景包括标记线程是否结束、双重检查锁定等。

  • volatile 能够替代 synchronized 吗?

不完全可以替代。volatile 主要用于确保变量的可见性,而 synchronized 用于确保互斥性。如果需要保证多线程下的互斥性,volatile 无法满足需求,需要使用 synchronized 或其他锁机制。通常,volatile 用于一些特定的场景,而 synchronized 用于更复杂的同步需求。

  • volatile 适用于什么类型的变量?

volatile 适用于布尔型、字节型、短整型、字符型、引用类型等变量。它通常不适用于 long 和 double 类型,因为它们在 Java 中不是原子操作,可能会引发一些线程安全问题。

  • 什么是指令重排序,volatile 如何避免它?

指令重排序是编译器或处理器为了提高性能而重新排序执行指令的顺序,但有时可能导致不正确的结果。volatile 可以避免指令重排序,因为它禁止编译器和处理器对标记为 volatile 的变量的读写指令进行重排序。

目录
相关文章
|
4天前
|
调度 云计算 芯片
云超算技术跃进,阿里云牵头制定我国首个云超算国家标准
近日,由阿里云联合中国电子技术标准化研究院主导制定的首个云超算国家标准已完成报批,不久后将正式批准发布。标准规定了云超算服务涉及的云计算基础资源、资源管理、运行和调度等方面的技术要求,为云超算服务产品的设计、实现、应用和选型提供指导,为云超算在HPC应用和用户的大范围采用奠定了基础。
179562 18
|
12天前
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
2024年11月29日,阿里云在上海举办金融量化策略回测Workshop,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。活动特别设计了动手实践环节,帮助参会者亲身体验阿里云产品功能,涵盖EHPC量化回测和Argo Workflows量化回测两大主题,旨在提升量化投研效率与安全性。
云上金融量化策略回测方案与最佳实践
|
13天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
9161 23
|
17天前
|
Cloud Native Apache 流计算
资料合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
4847 12
资料合集|Flink Forward Asia 2024 上海站
|
17天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
25天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
13天前
|
人工智能 容器
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
本文介绍了如何利用千问开发一款情侣刮刮乐小游戏,通过三步简单指令实现从单个功能到整体框架,再到多端优化的过程,旨在为生活增添乐趣,促进情感交流。在线体验地址已提供,鼓励读者动手尝试,探索编程与AI结合的无限可能。
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
|
13天前
|
消息中间件 人工智能 运维
12月更文特别场——寻找用云高手,分享云&AI实践
我们寻找你,用云高手,欢迎分享你的真知灼见!
993 67