【发布时间】:2011-12-09 22:48:07
【问题描述】:
Java 中的 volatile int 是线程安全的吗?也就是说,它可以在不加锁的情况下安全地读写吗?
【问题讨论】:
标签: java multithreading thread-safety volatile
Java 中的 volatile int 是线程安全的吗?也就是说,它可以在不加锁的情况下安全地读写吗?
【问题讨论】:
标签: java multithreading thread-safety volatile
是的,您可以安全地对其进行读取和写入 - 但您不能执行任何复合操作,例如安全地递增它,因为这是一个读取/修改/写入循环。还有一个问题是它如何与对其他变量的访问进行交互。
volatile 的确切性质坦率地令人困惑(请参阅 memory model section of the JLS for more details) - 我个人通常会改用 AtomicInteger,作为确保我做对的更简单的方法。 p>
【讨论】:
volatile int,但您需要将++ 替换为整个负载AtomicIntegerFieldUpdater。 (没有那么快,但如果访问主要由简单的读/写和/或内存开销很重要,那么它可能很有用)
[...] 是否能够在没有锁定的情况下安全地读取和写入?
是的,读取总是会产生最后一次写入的值,(并且读取和写入都是原子操作)。
易失性读/写在执行中引入了所谓的发生前关系。
来自 Java 语言规范 Chapter 17: Threads and Locks
对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前。
换句话说,在处理 volatile 变量时,您不必使用 synchronized 关键字显式同步(引入发生前关系)以确保线程获得写入变量的最新值。
正如 Jon Skeet 指出的那样,volatile 变量的使用是有限的,您通常应该考虑使用 java.util.concurrent 包中的类。
【讨论】:
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。
在 Java 中访问 volatile int 将是线程安全的。当我说访问时,我的意思是对它的单元操作,例如 volatile_var = 10 或 int temp = volatile_var (基本上用常量值写入/读取)。 java中的volatile关键字确保了两件事:
但是 Jon Skeet 正确地提到,在非原子操作 (volatile_var = volatile + 1) 中,不同的线程可能会得到意想不到的结果。
【讨论】:
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 无济于事。
【讨论】:
并非总是如此。
如果多个线程正在写入和读取变量,则不是线程安全的。如果您有一个写入线程和多个读取线程,则它是线程安全的。
如果您正在安全地寻找 Thread,请使用 AtomicXXX 类
支持对单个变量进行无锁线程安全编程的小类工具包。
本质上,这个包中的类将易失性值、字段和数组元素的概念扩展到那些还提供以下形式的原子条件更新操作的类:
boolean compareAndSet(expectedValue, updateValue);
请参阅下面帖子中的@teto 回答:
【讨论】:
如果 volatile 不依赖于任何其他 volatile 变量,则其线程安全以进行读取操作。万一写 volatile 不保证线程安全。
假设你有一个可变的变量 i,它的值取决于另一个可变的变量 j。现在 Thread-1 访问变量 j 并增加它,并且即将从 CPU 缓存中更新它在主内存中。如果 Thread-2 读取
Thread-1 之前的变量 i 实际上可以更新主内存中的 j。 i 的值将与 j 的旧值一致,这将是不正确的。它也称为脏读。
【讨论】: