Java 中的 volatile int 是线程安全的吗?

Posted

技术标签:

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

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

【问题讨论】:

【参考方案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关键字确保了两件事:

    读取时,您总是会在主内存中获得值。通常出于优化目的,JVM 使用寄存器或更一般的术语本地内存来存储/访问变量。所以在多线程环境中,每个线程可能会看到不同的变量副本。但是让它成为 volatile 可以确保对变量的写入刷新到主内存并从主内存读取它,从而确保线程看到变量的正确副本。 对 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 无济于事。

【讨论】:

在Java中,注意区分竞态条件和数据竞态 有趣的是,具有同步功能的“Helper”示例代码在带有 JIT 的古老 Java 版本(最多 5 个)上被破坏,请参阅 cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html【参考方案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 的旧值一致,这将是不正确的。它也称为脏读。

【讨论】:

以上是关于Java 中的 volatile int 是线程安全的吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java线程安全

java线程同步volatile与synchronized

java中的volatile变量

static volatile a b=c;

为啥 java 5+ 中的 volatile 不能确保来自另一个线程的可见性?

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)