繁体   English   中英

Java 线程安全中的 volatile int 是什么?

[英]Is a volatile int in Java thread-safe?

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

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

坦率地说, volatile 的确切性质令人困惑( 有关更多详细信息,请参阅JLS内存模型部分)——我个人通常会使用AtomicInteger来代替,作为确保我正确使用的一种更简单的方法。

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

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

易失性读/写在执行中引入了所谓的happens-before 关系。

来自 Java 语言规范第 17 章:线程和锁

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

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

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

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

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

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

1) 如果两个线程都在读取和写入共享变量,那么使用 volatile 关键字是不够的。 在这种情况下,您需要使用 synchronized 来保证变量的读写是原子的。 读取或写入 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

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

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

boolean compareAndSet(expectedValue, updateValue);

请参阅以下帖子中的@teto 答案:

挥发性布尔值与原子布尔值

如果 volatile 不依赖于任何其他 volatile 变量,则其线程安全可用于读取操作。 在写 volatile 的情况下不保证线程安全。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM