如何使用volatile变量编写一个简单的线程安全类?

Mat*_*ski 8 java concurrency multithreading java-memory-model

我想编写一个简单的线程安全类,可用于设置或获取Integer值.

最简单的方法是使用synchronized关键字:

public class MyIntegerHolder {

    private Integer value;

    synchronized public Integer getValue() {
        return value;
    }

    synchronized public void setValue(Integer value) {
        this.value = value;
    }

}
Run Code Online (Sandbox Code Playgroud)

我也可以尝试使用volatile:

public class MyIntegerHolder {

    private volatile Integer value;

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

}
Run Code Online (Sandbox Code Playgroud)

具有volatile关键字的类是否是线程安全的

考虑以下事件序列:

  1. 线程A将值设置为5.
  2. 线程B将值设置为7.
  3. 线程C读取值.

它遵循Java语言规范

  • "1" 发生在 "3" 之前
  • "2" 发生在 "3" 之前

但我不知道它是如何遵循规范"1" 发生 - 在 "2" 之前所以我怀疑"1" 不会 发生 - 在 "2" 之前.

我怀疑线程C可能读取7或5.我认为具有volatile关键字的类不是线程安全的,并且以下序列也是可能的:

  1. 线程A将值设置为5.
  2. 线程B将值设置为7.
  3. 线程C读取7.
  4. 线程D读取5.
  5. 线程C读取7.
  6. 线程D读取5.
  7. ...

假设具有volatile的 MyIntegerHolder 不是线程安全的,我是否正确?

是否可以使用AtomicInteger创建一个线程安全的整数持有者:

public class MyIntegerHolder {

    private AtomicInteger atomicInteger = new AtomicInteger();

    public Integer getValue() {
        return atomicInteger.get();
    }

    public void setValue(Integer value) {
        atomicInteger.set(value);
    }

}
Run Code Online (Sandbox Code Playgroud)

这是Java Concurrency In Practice一书的一个片段:

" 原子变量的读写与volatile变量具有相同的内存语义. "

编写线程安全的 MyIntegerHolder 的最佳(最好是非阻塞)方法是什么?

如果你知道答案,我想知道为什么你认为它是正确的.它符合规范吗?如果是这样,怎么样?

Mat*_*ski -1

这个问题对我来说并不容易,因为我认为(错误地)了解有关happens -before关系的所有内容可以使人完全理解Java内存模型以及易失性的语义。

我在这个文档中找到了最好的解释: “JSR-133:JavaTM内存模型和线程规范”

上述文档中最相关的片段是“7.3 Well-Formed Executions”部分。

Java 内存模型保证程序的所有执行都是格式正确的。仅当满足以下条件时,执行才是格式良好的:

  • 遵守先于发生的一致性
  • 遵守同步顺序一致性
  • ...(其他一些条件也必须成立)

Happens-before 一致性通常足以得出有关程序行为的结论 - 但在这种情况下不行,因为易失性写入不会发生在另一个易失性写入之前

带有volatile 的MyIntegerHolder是线程安全的,但它的安全性来自于同步顺序的一致性

在我看来,当线程 B 即将将该值设置为 7 时,A 不会通知 B 在那一刻之前它所做的所有事情(正如其他答案之一所建议的那样) - 它只通知 B 有关 volatile 变量的值。如果线程 B 执行的操作是读取而不是写入,则线程 A 会向 B 通知所有信息(为其他变量赋值)(在这种情况下,这两个线程执行的操作之间将存在发生前关系)。