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 的变量的读写指令进行重排序。

目录
打赏
0
3
3
1
74
分享
相关文章
|
2月前
|
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
96 0
【Java并发】【volatile】适合初学者体质的volatile
当你阅读dalao的框架源码的时候,你是否会见到这样一个关键字 - - - volatie,诶,你是否会好奇,为什么要加它?加了它有什么作用?
132 14
【Java并发】【volatile】适合初学者体质的volatile
|
4月前
|
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是写出高端的CRUD应用。2025年,我正在沉淀自己,博客更新速度也在加快。在这里,我会分享关于Java并发编程的深入理解,尤其是volatile关键字的底层原理。 本文将带你深入了解Java内存模型(JMM),解释volatile如何通过内存屏障和缓存一致性协议确保可见性和有序性,同时探讨其局限性及优化方案。欢迎订阅专栏《在2B工作中寻求并发是否搞错了什么》,一起探索并发编程的奥秘! 关注我,点赞、收藏、评论,跟上更新节奏,让我们共同进步!
243 8
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
|
5月前
|
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
108 21
|
3月前
|
深入理解 Java 中的 instanceof 关键字
本文深入解析了 Java 中的 `instanceof` 关键字,探讨其在类型判断中的作用。作为二元操作符,`instanceof` 可用于检查对象是否为某类实例或实现特定接口,避免类型转换异常 (`ClassCastException`)。文章通过多态性下的类型判断、安全类型转换、接口实现检测及集合元素类型判定等实际应用场景,展示了 `instanceof` 的强大功能。掌握该关键字可提高代码健壮性,确保运行时类型安全。
148 0
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
1032 9
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
217 5
Java 并发编程——volatile 关键字解析
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
164 7
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问