Java 中的 volatile 关键字

Mohd Mohtashim Nawaz 2023年10月12日
  1. Java 中的 volatile 关键字
  2. 在 Java 中我们什么时候使用 volatile 关键字
  3. Java 中 volatile 关键字的性能优势
  4. 在 Java 中使用 volatile 关键字
  5. 结论
Java 中的 volatile 关键字

Java 是一种非常流行的编程语言,通过了解 Java,我们可以很容易地理解它为什么会在编程社区中获得这样的地位。

Java 为我们提供了大量有用的资源,我们可以在程序中使用这些资源来编写所需的逻辑。

Java 对于多用途编程问题很有用,其中一个这样的编程问题是使用线程或不同逻辑程序的并行执行。

本文将探索和理解 Java 中的 volatile 关键字、它的属性以及它在实际编程中的用法。我们将了解何时使用 volatile 关键字、其重要性以及何时以及不应使用。

本文还将提供一个基本的示例代码来帮助你理解 volatile 关键字。

Java 中的 volatile 关键字

在使用 Java 时,你可能遇到过并行线程和 synchronized 关键字。volatile 关键字与 synchronized 关键字相似,但又完全不同。

volatile 关键字在我们的代码中很容易理解和实现,并且比 synchronized 锁定机制具有更少的运行时开销。然而,volatile 关键字不如 synchronized 锁定强大。

volatile 关键字确保对共享 volatile 变量所做的更改立即反映在所有线程中。synchronized 关键字用于实现 Java 中的锁机制,锁提供可见性和原子性。

但是,volatile 关键字只能实现可见性,不能实现原子性。因此,我们可以理解,volatile 关键字只能在极少数情况下使用。

例如,当变量的值独立于所有其他变量和案例,甚至独立于自身时,我们可以使用它。

在 Java 中我们什么时候使用 volatile 关键字

正如我们已经看到的,在 volatile 关键字的帮助下,共享变量的更改值会立即反映在所有其他线程中。因此,我们在处理线程中的共享变量时使用它。

synchronized 关键字已经可用时,你可能会问为什么我们使用 volatile 关键字?但是,volatile 关键字更易于理解且性能更好。

因此,在某些情况下,我们可能想要使用它。例如,当我们有大量的读取操作和少量的写入操作时,我们希望使用 volatile 关键字以获得更好的性能。

此外,volatile 关键字提供了更好的可伸缩性,因为它不会导致线程阻塞。

为了更好地理解我们可以安全有效地使用 volatile 关键字的情况,我们应该注意以下情况。

  • 程序的当前和其他变量和状态之间不应存在相互依赖关系。
  • 变量不应依赖于自身。

因此,如果我们的变量完全独立于自身,我们可以使用代码的 volatile 关键字。

但是,我们经常遇到不满足第一个或第二个条件或两个条件的情况。因此,volatile 关键字的使用受到很大限制。

请注意许多程序员犯的一个非常微妙的错误。很多时候,程序员倾向于将增量运算符视为单个操作。

但是,增量运算符(或减量运算符)由多个操作组成,并且发生在多个阶段。因此,在这种情况下,我们不应该使用 volatile 关键字。

Java 中 volatile 关键字的性能优势

虽然 volatile 关键字简单易用,但它的性能优于锁定机制。在某些情况下,可以看出 volatile 的性能比 synchronized 好得多。

然而,事情并没有那么简单。当我们有 Java 虚拟机(JVM)时,很难正确判断这两个关键字的性能。

是因为 JVM 进行了优化,有时候加锁是不需要的,JVM 在代码优化的时候把它去掉了。但是,通常观察到易失性读取在性能方面非常好,并且可以与普通读取相媲美。

然而,易失性写入比普通写入要昂贵得多。另一方面,易失性写入总是比获取锁便宜。

因此,我们应该考虑 volatile 关键字和 synchronized 锁定机制之间的性能比较。

我们已经讨论了 volatile 的另一个性能优势:它不会导致线程阻塞并且在可扩展性方面要好得多。这是使用 volatile 关键字的另一个原因。

在 Java 中使用 volatile 关键字

我们将在下面的代码中了解如何在我们的 Java 代码中使用 volatile 关键字。

sharedObj.java

public class sharedObj {
  volatile boolean flag;
}

ThClass.java

public class ThClass extends Thread {
  public sharedObj obj;
  public ThClass(sharedObj obj) {
    this.obj = obj;
  }

  @Override
  public void run() {
    super.run();
    while (true) {
      if (this.obj.flag)
        this.obj.flag = false;
      else
        this.obj.flag = true;
      System.out.println(obj.flag);
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

主类.java

import java.util.*;
public class MainClass {
  public static void main(String[] args) {
    sharedObj obj = new sharedObj();
    ThClass th1, th2;
    th1 = new ThClass(obj);
    th2 = new ThClass(obj);
    th1.run();
    th2.run();
  }
}

在上面给出的 Java 程序中,我们创建了三个不同的类。这些类解释如下。

  • sharedObj.java:我们已经声明了一个在这个类中的线程之间共享的可变共享变量。
  • ThClass.java:这个类用于创建线程。它扩展了 Thread 类并覆盖了 run() 方法。每个线程休眠 1 秒。
  • MainClass.javaMain 类是实现 main() 方法的最重要的类,从执行开始。我们创建了两个线程,并通过构造函数传递了一个共享对象作为参数。

该 Java 程序的输出如下所示。

true
false
true
false
true
false

请注意,此代码运行无限时间,要停止它,你必须手动停止它的执行。 (例如,在 Linux 上按 CTRL+C)。

结论

在本文中,我们了解了 volatile 关键字、它的优缺点以及何时可以使用它。虽然 volatile 关键字不如同步锁定机制强大,但它仍然在实践中使用。

我们仍然使用 volatile 关键字,因为它在代码中更容易理解和实现,比锁具有更好的性能,并且更容易扩展,因为它不锁定线程。

但是,在代码中使用 volatile 关键字时,应始终小心检查条件,以免发生意外错误。

本文提到了检查 volatile 关键字是否适用的简单步骤;检查使用这些步骤以了解 volatile 是否适用于你的代码。

这将有助于在同步锁定机制和 volatile 关键字之间进行选择。