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

标签 java multithreading concurrency java-memory-model

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

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

public class MyIntegerHolder {

    private Integer value;

    synchronized public Integer getValue() {
        return value;
    }

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

}

我也可以尝试使用 volatile:

public class MyIntegerHolder {

    private volatile Integer value;

    public Integer getValue() {
        return value;
    }

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

}

带有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 制作线程安全的 Integer holder:

public class MyIntegerHolder {

    private AtomicInteger atomicInteger = new AtomicInteger();

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

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

}

?

这是《Java 并发实践》一书的片段:

"Reads and writes of atomic variables have the same memory semantics as volatile variables."

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

如果你知道答案,我想知道你为什么认为它是正确的。它是否遵循规范?如果是,怎么办?

最佳答案

关键字synchronized 表示如果Thread A 和Thread B 想要访问Integer,它们不能同时访问。 A 正在告诉 B 等我完成。

另一方面,volatile让线程更加“友好”。他们开始互相交谈并一起工作以执行任务。因此,当 B 尝试访问时,A 会通知 B 它在那一刻之前所做的一切。 B 现在知道这些变化并且可以从 A 离开的地方继续它的工作。

在 Java 中,你有 Atomic 因为这个原因,它在幕后使用了 volatile 关键字,所以他们做的事情几乎是一样的,但他们救了你时间和精力。

你正在寻找的东西是AtomicInteger,你是对的。对于您尝试执行的操作,这是最佳选择。

There are two main uses of `AtomicInteger`:

 * As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently

 * As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. 

一般性地回答你的问题

这取决于你需要什么。我并不是说 synchronized 是错误的,而 volatile 是好的,否则那些善良的 Java 人早就删除了 synchronized。没有绝对的答案,有很多具体的案例和使用场景。

我的一些书签:

Concurrency tips

Core Java Concurrency

Java concurrency

更新

来自可用的 Java 并发规范 here :

Package java.util.concurrent.atomic

A small toolkit of classes that support lock-free thread-safe programming on single variables.

Instances of classes `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, and `AtomicReference` each provide access and updates to a single variable of the corresponding type.
Each class also provides appropriate utility methods for that type.
For example, classes `AtomicLong` and AtomicInteger provide atomic increment methods.

The memory effects for accesses and updates of atomics generally follow the rules for volatiles:

get has the memory effects of reading a volatile variable.
set has the memory effects of writing (assigning) a volatile variable.

还有来自Here

Java 编程语言 volatile 关键字:

(在所有版本的 Java 中)对 volatile 变量的读写有一个全局顺序。这意味着访问 volatile 字段的每个线程将在继续之前读取其当前值,而不是(可能)使用缓存值。 (但是,无法保证 volatile 读取和写入与常规读取和写入的相对顺序,这意味着它通常不是有用的线程构造。)

关于java - 如何使用 volatile 变量编写简单的线程安全类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16358615/

相关文章:

java - Eclipse 发生错误。请参阅日志文件 (Windows x64)

java - 字符串和新字符串有什么区别?

java - 如何在开发过程中在一个地方捕获来自不同线程的所有未捕获异常?

java - 如何获取kafka的消费者offset值

java - 具有不同参数的抽象函数

java - 读取新值后读取旧值

Java:ActionListener.actionPerformed 中的并发

java - 如何在并行流上使用java map-reduce组合器

Java 线程概念 : Output Explanation

c++ - 如何在pthread线程中创建memcpy?