Java 中的 Volatile 是如何工作的?
什么是Java中的 volatile 变量以及什么时候使用Java中的 volatile 变量是Java面试中著名的多线程面试题? 尽管许多程序员知道什么是 volatile 变量,但他们在第二部分失败了,即在 Java 中使用 volatile 变量,因为在 Java 中对 volatile 变量有清晰的理解和实践并不常见。
在本篇文章中,我们将通过提供 Java 中 volatile 变量的简单示例并讨论何时在 Java 中使用 volatile 变量来弥补这一差距。 无论如何,Java 中的 volatile 关键字用作指示 Java 编译器和 Thread 不缓存此变量的值并始终从主内存中读取它。
因此,如果大家想通过在 int
或 boolean
变量中读取和写入等实现来共享读写操作是原子的任何变量,那么我们可以将它们声明为 volatile
变量。
从 Java 5 开始,随着自动装箱、枚举、泛型和变量参数等重大变化,Java 在 Java 内存模型 (JMM) 中引入了一些变化,这保证了从一个线程到另一个线程所做的更改的可见性,也作为“先于发生” 解决了发生在一个线程中的内存写入可以“泄漏”并被另一个线程看到的问题。
Java volatile 关键字不能与方法或类一起使用,它只能与变量一起使用。 Java volatile 关键字还保证可见性和顺序,在 Java 5 写入任何 volatile 变量之后发生在任何读入 volatile 变量之前。
顺便说一句,使用 volatile 关键字还可以防止编译器或 JVM 对代码进行重新排序或将它们从同步屏障中移开。
Java 中的 Volatile 变量示例
为了理解 java 中 volatile 关键字的例子,让我们回到 Java 中的单例模式,看看在单例中使用 Volatile 和 java 中没有 volatile 关键字的双重检查锁定。
/**
* 用于演示在 Java 中何处使用 Volatile 关键字的 Java 程序。
* 在此示例中,Singleton Instance 被声明为 volatile 变量,
* 以确保每个线程都能看到 _instance 的更新值。
*
* @author Jiyik
*/
public class Singleton{
private static volatile Singleton _instance; //volatile variable
public static Singleton getInstance(){
if(_instance == null){
synchronized(Singleton.class){
if(_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
如果仔细查看代码,我们将能够弄清楚:
- 我们只创建一次实例
- 我们在第一个请求到来时懒惰地创建实例。
如果我们不让 _instance
变量为 volatile,那么正在创建 Singleton
实例的 Thread 将无法与其他线程通信,该实例已经创建,直到它从 Singleton
块中出来,所以如果 Thread A 正在创建 Singleton
实例并且 就在创建失去 CPU 之后,所有其他线程将无法将 _instance
的值视为不为空,并且它们会认为它仍然为空。

为什么? 因为读取线程没有进行任何锁定,并且在写入线程退出同步块之前,内存不会同步,并且 _instance
的值不会在主内存中更新。
使用 Java 中的 Volatile 关键字,这由 Java 自己处理,并且所有读取器线程都可以看到此类更新。 所以在总结中除了Java中的synchronized
关键字外,还使用了一个 volatile 关键字来实现线程间内存内容的通信。
让我们看一下 Java 中 volatile 关键字的另一个例子
大多数时候在编写游戏时我们使用变量 bExit 来检查用户是否按下了退出按钮,这个变量的值在事件线程中更新并在游戏线程中检查,所以如果我们不使用 带有此变量的 volatile 关键字,如果 Game Thread 尚未在 Java 中同步,它可能会错过来自事件处理程序线程的更新。
java中的 volatile 关键字保证了 volatile 变量的值总是从主存中读取,Java内存模型中的“happens-before”关系保证了内存中的内容会被传递给不同的线程。
private boolean bExit;
while(!bExit) {
checkUserPosition();
updateUserPosition();
}
在此代码示例中,一个线程(游戏线程)可以缓存“bExit”的值,而不是每次都从主内存中获取它,如果在任何其他线程(事件处理程序线程)之间更改该值; 该线程将看不到它。 在 Java 中将布尔变量“bExit”设置为 volatile 可确保不会发生这种情况。
什么时候在 Java 中使用 Volatile 变量?
学习 volatile 关键字最重要的事情之一是了解何时在 Java 中使用 volatile 变量。 许多程序员知道什么是 volatile 变量以及它是如何工作的,但他们从未真正将 volatile 修饰符用于任何实际目的。 下面是几个示例来演示何时在 Java 中使用 volatile 关键字:
1. 如果你想原子地读写long
和double
变量,你可以使用 Volatile 变量。 long
和 double
都是 64 位数据类型,默认情况下 long
和 double
的写入不依赖于原子和平台。
许多平台在 long
和 double
变量 2 步骤中执行写入,在每个步骤中写入 32 位,因此线程可能会看到来自两个不同写入器的 32 位。 我们可以通过在 Java 中将 long
和 double
变量设置为 volatile 来避免这个问题。
2. 在某些情况下,volatile 变量可以用作在 Java 中实现同步的替代方法,例如 Visibility
。 使用 volatile 变量,可以保证一旦写入操作完成,所有读取线程都会看到 volatile 变量的更新值,如果没有 volatile 关键字,不同的读取线程可能会看到不同的值。
3. volatile 变量可用于通知编译器某个特定字段可能会被多个线程访问,这将阻止编译器进行任何重新排序或任何类型的优化,这在多线程环境中是不可取的。
如果没有 volatile 变量,编译器可以重新排序代码,自由缓存 volatile 变量的值,而不是总是从主内存中读取。 像下面没有 volatile 变量的例子可能会导致无限循环
private boolean isActive = thread;
public void printMessage(){
while(isActive){
System.out.println("Thread is Active");
}
}
如果没有 volatile 修饰符,则不能保证一个线程从其他线程看到 isActive
的更新值。 编译器也可以自由缓存 isActive
的值,而不是在每次迭代时从主内存中读取它。 通过使 isActive
成为 volatile 变量,我们可以避免这些问题。
4. 另一个可以使用 volatile 变量的地方是修复单例模式中的双重检查锁定。 正如我们在 Why should you use Enum as Singleton 中讨论的那样,双重检查锁定在 Java 1.4 环境中被破坏了?
Java 中 Volatile 关键字的要点
- Java中的 volatile 关键字是唯一对变量的应用,在类和方法中使用 volatile 关键字是非法的。
- Java中的 volatile 关键字保证 volatile 变量的值总是从主存中读取,而不是从Thread的本地缓存中读取。
-
在 Java 中,对于所有使用 Java volatile 关键字声明的变量(包括
long
和double
变量),读写都是原子的。 - 在 Java 中对变量使用 volatile 关键字可以降低内存一致性错误的风险,因为在 Java 中对 volatile 变量的任何写入都会与对该相同变量的后续读取建立先行关系。
- 从 Java 5 开始,对 volatile 变量的更改始终对其他线程可见。 更重要的是,这也意味着当线程读取 Java 中的 volatile 变量时,它不仅会看到对 volatile 变量的最新更改,还会看到导致更改的代码的副作用。
-
即使在 Java 中没有使用 volatile 关键字,对于大多数原始变量(除
long
和double
之外的所有类型),引用变量的读写都是原子的。 - 访问 Java 中的 volatile 变量永远不会有阻塞的机会,因为我们只是在进行简单的读取或写入,所以与同步块不同,我们永远不会持有任何锁或等待任何锁。
-
作为对象引用的 Java volatile 变量可能为
null
。 -
Java volatile 关键字并不意味着原子,这是一个常见的误解,认为在声明
volatile ++
将是原子的之后,要使操作成为原子,我们仍然需要使用 Java 中的同步方法或块来确保独占访问。 - 如果一个变量不在多个线程之间共享,则不需要对该变量使用 volatile 关键字。
Java 中 synchronized 和 volatile 关键字的区别
volatile 和 synchronized 之间的区别是多线程和并发面试中另一个流行的核心 Java 问题。 请记住,volatile 不是同步关键字的替代品,但在某些情况下可以用作替代品。
以下是 Java 中 volatile 和 synchronized 关键字之间的一些区别。
- Java中的 volatile 关键字是字段修饰符,synchronized 修饰的是代码块和方法。
-
synchronized 获取和释放
monitor
的锁,Java的 volatile 关键字不需要。 - Java中的线程在同步的情况下可以阻塞等待任何监视器,而Java中的 volatile 关键字则不会。
- synchronized 方法比 Java 中的 volatile 关键字更能影响性能。
- 由于Java中的 volatile 关键字只同步线程内存和“主”内存之间的一个变量的值,而synchronized同步线程内存和“主”内存之间的所有变量的值并锁定和释放监视器以启动。 由于这个原因,Java 中的 synchronized 关键字可能比 volatile 有更多的开销。
- 我们不能在空对象上进行同步,但 Java 中的 volatile 变量可能为空。
- 从 Java 5 开始,写入 volatile 字段与监视器释放具有相同的记忆效应,从 volatile 字段读取与监视器获取具有相同的记忆效应
简而言之,Java 中的 volatile 关键字不是同步块或方法的替代品,但在某些情况下非常方便,并且可以节省 Java 中使用同步带来的性能开销。
相关文章
Do you understand JavaScript closures?
发布时间:2025/02/21 浏览次数:108 分类:JavaScript
-
The function of a closure can be inferred from its name, suggesting that it is related to the concept of scope. A closure itself is a core concept in JavaScript, and being a core concept, it is naturally also a difficult one.
Do you know about the hidden traps in variables in JavaScript?
发布时间:2025/02/21 浏览次数:178 分类:JavaScript
-
Whether you're just starting to learn JavaScript or have been using it for a long time, I believe you'll encounter some traps related to JavaScript variable scope. The goal is to identify these traps before you fall into them, in order to av
How much do you know about the Prototype Chain?
发布时间:2025/02/21 浏览次数:150 分类:JavaScript
-
The prototype chain can be considered one of the core features of JavaScript, and certainly one of its more challenging aspects. If you've learned other object-oriented programming languages, you may find it somewhat confusing when you start
如何在 JavaScript 中合并两个数组而不出现重复的情况
发布时间:2024/03/23 浏览次数:86 分类:JavaScript
-
本教程介绍了如何在 JavaScript 中合并两个数组,以及如何删除任何重复的数组。