【问题标题】:Is a volatile int in Java thread-safe?Java 中的 volatile int 是线程安全的吗?
【发布时间】:2011-12-09 22:48:07
【问题描述】:

Java 中的 volatile int 是线程安全的吗?也就是说,它可以在不加锁的情况下安全地读写吗?

【问题讨论】:

    标签: java multithreading thread-safety volatile


    【解决方案1】:

    是的,您可以安全地对其进行读取和写入 - 但您不能执行任何复合操作,例如安全地递增它,因为这是一个读取/修改/写入循环。还有一个问题是它如何与对其他变量的访问进行交互。

    volatile 的确切性质坦率地令人困惑(请参阅 memory model section of the JLS for more details) - 我个人通常会改用 AtomicInteger,作为确保我做对的更简单的方法。 p>

    【讨论】:

    • 您可以安全地增加volatile int,但您需要将++ 替换为整个负载AtomicIntegerFieldUpdater。 (没有那么快,但如果访问主要由简单的读/写和/或内存开销很重要,那么它可能很有用)
    • 为了更全面地说明 ++ 问题,我发现 jeremymanson.blogspot.com/2007/08/… 很好很清晰。
    • 当只有 1 个线程执行该操作时,对 volatile 的复合操作 (++) 是线程安全的。然后另一个线程可以安全地读取该值。
    • 不要吹毛求疵,但当只有 1 个线程执行时,一切都是线程安全的。
    • @npace:Sumit 谈到了一个线程写入而另一个线程读取的场景。
    【解决方案2】:

    [...] 是否能够在没有锁定的情况下安全地读取和写入?

    是的,读取总是会产生最后一次写入的值,(并且读取和写入都是原子操作)。

    易失性读/写在执行中引入了所谓的发生前关系。

    来自 Java 语言规范 Chapter 17: Threads and Locks

    对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前。

    换句话说,在处理 volatile 变量时,您不必使用 synchronized 关键字显式同步(引入发生前关系)以确保线程获得写入变量的最新值。

    正如 Jon Skeet 指出的那样,volatile 变量的使用是有限的,您通常应该考虑使用 java.util.concurrent 包中的类。

    【讨论】:

    • "处理易失性变量时 (...) 写入变量的最新值" 和 non volatile 变量,没有“写入变量的最新值”之类的东西。您需要 volatile 来表示“写入变量的最新值”这句话才有意义。
    • 我不同意。如果我在一个线程中执行x = 1; x = 2;,然后在另一个线程中执行System.out.println(x)很久之后 x = 2 已被执行,它可能仍会打印1。我的意思是如果@987654330 @ 是最后写入 x 的值,然后 x 在另一个线程中可能仍无法计算为 2
    • 如果x 不是volatile: "x = 2 被执行后"/x = 2 被执行前定义不明确。如果你真的在x被设置为2之后阅读,那么你当然会得到2。
    【解决方案3】:

    在 Java 中访问 volatile int 将是线程安全的。当我说访问时,我的意思是对它的单元操作,例如 volatile_var = 10 或 int temp = volatile_var (基本上用常量值写入/读取)。 java中的volatile关键字确保了两件事:

    1. 读取时,您总是会在主内存中获得值。通常出于优化目的,JVM 使用寄存器或更一般的术语本地内存来存储/访问变量。所以在多线程环境中,每个线程可能会看到不同的变量副本。但是让它成为 volatile 可以确保对变量的写入刷新到主内存并从主内存读取它,从而确保线程看到变量的正确副本。
    2. 对 volatile 的访问会自动同步。因此,JVM 在读/写变量时确保了排序。

    但是 Jon Skeet 正确地提到,在非原子操作 (volatile_var = volatile + 1) 中,不同的线程可能会得到意想不到的结果。

    【讨论】:

      【解决方案4】:

      1) 如果两个线程同时读取和写入共享变量,那么使用 volatile 关键字是不够的。在这种情况下您需要使用同步来保证变量的读取和写入是原子的。读取或写入 volatile 变量不会阻塞线程读取或写入。为此,您必须在关键部分周围使用 synchronized 关键字。

      2) 作为同步块的替代方案,您还可以使用 java.util.concurrent 包中的许多原子数据类型之一。例如,AtomicLong 或 AtomicReference 或其他之一。

      如果您有一个写入线程和多个读取线程,则它是线程安全的。

      class Foo {
      private volatile Helper helper = null;
      public Helper getHelper() {
      if (helper == null) {
      synchronized(this) {
      if (helper == null)
      helper = new Helper();
      }
      }
      return helper;
      }
      }
      

      注意:如果 helper 是不可变的,则不需要 volatile 关键字。这里单例可以正常工作。

      如果计数器被多个线程递增(读写操作)将不会给出正确答案。竞争条件也说明了这种情况。

      public class Counter{
      private volatile int i;
      public int increment(){
      i++;
      }
      }
      

      注意:这里 volatile 无济于事。

      【讨论】:

      【解决方案5】:

      并非总是如此。

      如果多个线程正在写入和读取变量,则不是线程安全的。如果您有一个写入线程和多个读取线程,则它是线程安全的。

      如果您正在安全地寻找 Thread,请使用 AtomicXXX

      支持对单个变量进行无锁线程安全编程的小类工具包。

      本质上,这个包中的类将易失性值、字段和数组元素的概念扩展到那些还提供以下形式的原子条件更新操作的类:

      boolean compareAndSet(expectedValue, updateValue);
      

      请参阅下面帖子中的@teto 回答:

      Volatile boolean vs AtomicBoolean

      【讨论】:

      • @Ravindrababu - “如果多个线程正在写入和读取变量,这不是线程安全的” - 我认为这不是真的。整数的更新是原子操作,因此即使多个线程正在写入它,每个线程都会看到最新状态(鉴于其易失性)。订单虽然不能保证,但需要锁定......
      【解决方案6】:

      如果 volatile 不依赖于任何其他 volatile 变量,则其线程安全以进行读取操作。万一写 volatile 不保证线程安全。

      假设你有一个可变的变量 i,它的值取决于另一个可变的变量 j。现在 Thread-1 访问变量 j 并增加它,并且即将从 CPU 缓存中更新它在主内存中。如果 Thread-2 读取
      Thread-1 之前的变量 i 实际上可以更新主内存中的 j。 i 的值将与 j 的旧值一致,这将是不正确的。它也称为脏读。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-07
        • 1970-01-01
        • 2010-12-16
        • 2016-08-24
        • 1970-01-01
        相关资源
        最近更新 更多