Java 中的 volatile 关键字

2025年7月21日 | 阅读 7 分钟

在 Java 中,volatile 关键字用于指示变量的值可能被不同的线程异步修改。它还用于使类具有线程安全性。这意味着多个线程可以同时使用一个方法和类的实例而不会出现任何问题。它可以与基本类型或对象一起使用,但不能与类或方法一起使用。它不会缓存变量的值,而是始终从主内存读取变量。

然而,它用于变量。本质上,它保证了对变量的读写操作的可见性和原子性。它阻止编译器重新排序代码。

特定设备寄存器的内容可能随时更改,因此您需要 volatile 关键字来确保编译器不会优化掉此类访问。

示例

在上面的示例中,假设有两个线程正在处理同一个类。两个线程都在不同的处理器上运行,每个线程都有变量 var 的本地副本。如果任何线程修改了其值,则更改将不会反映在主内存中的原始值中。这会导致数据不一致,因为另一个线程不知道修改后的值。

在上面的示例中,静态变量是所有对象共享的类成员。主内存中只有一个副本。volatile 变量的值将永远不会存储在缓存中。所有读写操作都将从主内存进行,并写入主内存。

示例:volatile 关键字

让我们考虑一个多个线程在没有适当同步的情况下访问共享变量的场景。在没有同步的情况下,一个线程所做的更新可能对其他线程不可见,从而导致意外行为。以下是 volatile 关键字如何提供帮助。我们已经定义了一个增加计数器值的类。VolatileThread.java 中的 run() 方法在线程开始执行时获取更新后的值和旧值。在 main 类中,我们定义了线程数组。

文件名: VolatileData.java

文件名: VolatileThread.java

文件名: Main.java

示例

编译并运行

输出

[Thread 22]: Old value = 0
[Thread 22]: New value = 1
[Thread 21]: Old value = 0
[Thread 21]: New value = 2

在 Java 中,volatile 关键字通过确保多个线程之间共享变量的可见性和一致性,在并发编程中起着至关重要的作用。通过使用 volatile,开发人员可以避免与过时或不一致数据相关的微妙并发错误。但是,重要的是要明智地使用 volatile 并理解其局限性。

何时使用它?

在变量由多个线程共享,并且其值由一个线程频繁更新而由其他线程读取的情况下,volatile 关键字尤其有用。volatile 变量的常见用例包括控制线程执行的标志、状态指示器以及双重检查锁定模式中使用的简单变量。

  • 如果我们想读写 long 和 double 变量,我们可以使用 volatile 变量
  • 它可以作为 Java 中实现同步的替代方法。
  • 所有读取线程将在完成写操作后看到 volatile 变量的更新值。如果我们不使用 volatile 关键字,不同的读取线程可能会看到其他
  • 它用于通知编译器多个线程将访问特定语句。它阻止编译器进行任何重新排序或优化。
  • 如果我们不使用 volatile 变量,编译器可以重新排序代码,并且可以自由地将值写入 volatile 变量的缓存,而不是从主内存读取。

要记住的重要事项

  • 我们可以将 volatile 关键字与变量一起使用。在类和方法上使用 volatile 关键字是违法的。
  • 它保证 volatile 变量的值将始终从主内存读取,而不是从本地线程缓存读取。
  • 如果我们声明一个变量为 volatile,读写操作都是原子的
  • 它降低了内存一致性错误的风险。
  • 对 volatile 变量的任何写入都会建立与该变量的后续读取的“发生之前”关系。
  • 当一个线程写入 volatile 变量时,写操作会立即对其他线程可见。
  • volatile 变量,该变量是一个对象引用,可能为 null。
  • 当一个变量不被多个线程共享时,我们不需要在该变量上使用 volatile 关键字。

synchronized 和 volatile 关键字之间的区别

volatile 关键字不是 synchronized 关键字的替代品,但它可以在某些情况下用作替代。它们之间的区别如下:

volatile 关键字synchronized 关键字
volatile 关键字是字段修饰符。synchronized 关键字修饰代码块和方法。
在 volatile 的情况下,线程不会因等待而被阻塞。在 synchronized 的情况下,线程可能会因等待而被阻塞。
它提高了线程性能。Synchronized 方法会降低线程性能。
它在一个时间点同步线程内存和主内存之间的单个变量的值。它同步线程内存和主内存之间的所有变量的值。
Volatile 字段不受编译器优化。Synchronize 受编译器优化。
它不提供互斥;多个线程可以同时访问该变量。它提供互斥;一次只有一个线程可以访问同步的代码块或方法。
它适用于线程仅对单个变量执行读/写操作的情况。它适用于涉及多个变量或复合操作(读-修改-写)的场景。
它不能用于确保复合操作的原子性。它确保整个代码块或方法的原子性。
轻量级,对系统资源的消耗更少。由于锁定机制,资源消耗更大。
它不能参与使用 wait/notify 的线程间信号。它可以使用 wait()、notify() 和 notifyAll() 进行线程间信号。

结论

volatile 关键字建立线程之间的可见性,以最大程度地减少内存一致性错误。volatile 关键字允许所有线程立即看到写操作,当特定情况需要时,它比锁定机制更简单。

volatile 关键字广泛用于基本标志以及指示变量和读/写操作,但不能生成原子数据操作。用户应仅在明确理解其潜在问题以及正确应用场景的情况下才能正确使用 volatile。正确应用 volatile 可以为构建并发应用程序增加安全性和效率。

volatile 关键字选择题

1. 关于 Java 中的 volatile 关键字,以下哪个说法是正确的?

  1. 它保证了复合操作的原子性。
  2. 它确保了变量更改在线程之间的可见性。
  3. 它可以与方法和类一起使用。
  4. 它强制所有变量进行同步。
 

答案:B

解释: volatile 确保变量的值始终从主内存读取,提供可见性,但不能保证像 x++ 这样的复合操作的原子性。


2. 当 Java 中一个变量被声明为 volatile 时会发生什么?

  1. 每个线程都缓存该变量。
  2. 该变量只能由声明它的线程访问。
  3. 读写操作始终在主内存上执行。
  4. 线程会自动同步。
 

答案:C

解释: 声明一个变量为 volatile 会告诉 JVM 不要将变量缓存到线程本地内存中,确保它始终从主内存获取。


3. 当一个线程写入 volatile 变量时会发生什么?

  1. 它会锁定该变量以防止访问。
  2. 它会在线程本地内存中缓存该变量。
  3. 它会立即使写入对所有其他线程可见。
  4. 它会优化访问路径以提高速度。
 

答案:C

解释: 写入 volatile 变量会建立一个“发生之前”关系,使更改立即对所有线程可见。


4. 在哪种情况下应该使用 volatile 关键字?

  1. 当变量不被线程共享时。
  2. 当只有一个线程读取和写入该变量时。
  3. 当多个线程读取和写入共享变量,并且您想确保可见性时。
  4. 当您想阻止线程访问变量时。
 

答案:C

解释: volatile 适用于由一个线程写入而由其他线程读取的共享变量,以确保最新值的可见性。


5. Java 中 synchronized 和 volatile 有什么区别?

  1. 两者都提供原子性和可见性。
  2. volatile 会阻塞其他线程;synchronized 不会。
  3. synchronized 提供可见性和原子性;volatile 只提供可见性。
  4. volatile 提供比 synchronized 更好的控制。
 

答案:C

解释: volatile 确保可见性,而 synchronized 提供可见性和原子性,使其更适合关键部分。