如果从 2 个线程写入 volatile 变量会怎样?

What happens if a volatile variable is written from 2 threads?

考虑Java 并发实践-

中的片段
@ThreadSafe
public class SynchronizedInteger{
    @GuardedBy("this") private int value;

    public synchronized int getValue() {
        return value;
    }

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

同一本书的节选-

A good way to think about volatile variables is to imagine that they behave roughly like the SynchronizedInteger class in Listing above, replacing reads and writes of the volatile variable with calls to get and set. Yet accessing a volatile variable performs no locking and so cannot cause the executing thread to block, making volatile variables a lighter-weight synchronization mechanism than synchronized.

线程限制的一个特例适用于可变变量。只要确保 volatile 变量仅从单个线程写入,就可以安全地对共享的 volatile 变量执行读取-修改-写入操作。

所以,如果你把上面class中的实例变量设为volatile,然后去掉synchronized关键字,之后假设有3个线程

线程 A 和线程 B 正在写入同一个 volatile 变量。
线程C读取volatile变量

既然volatile变量现在是从2个线程写入的,为什么对这个共享的volatile变量执行读-修改-写操作是不安全的?

这是因为 volatile 变量的读-修改-写操作不是原子的。 v++ 实际上是这样的:

r1 = v;
r2 = r1 + 1;
v = r2;

因此,如果您有两个线程各执行一次此操作,则可能导致变量仅递增一次,因为它们都读取旧值。这就是为什么它不安全的一个例子。

在您的示例中,如果您删除同步、使字段易变并且有两个线程在基于 getValue 的 return 的一些条件逻辑之后调用 setValue 将不安全 - 该值可能已被修改另一个线程。

如果您想要原子操作,请查看 java.util.concurrent.atomic 包。

关键字 volatile 用于确保对您的 Object 所做的更改将被其他 Thread 看到。 这并不强制执行 Object 上的非原子操作,在操作完成之前将在没有其他 Thread 干扰的情况下执行。 为了强制执行此操作,您将需要关键字 synchronized

如果您在不使用任何同步结构的情况下从多个线程写入 volatile 变量,您一定会遇到数据不一致的错误。

在原子操作的单写线程和多读线程的情况下使用不同步的易失变量。

Volatile 确保从主内存而不是线程缓存中获取变量值。在单写多读操作的情况下使用是安全的。

使用原子变量或同步或锁定 API 从多个线程更新和读取变量。

参考相关SE问题:

What is meant by "thread-safe" code?

如果两个线程不先读取变量而写入,则没有问题..它是安全的。如果线程先读取,然后修改然后写入,就会出现问题。如果第二个线程也在同时读取,读取与第一个线程相同的旧值,并对其进行修改……当它写入时,它会简单地覆盖第一个线程的更新。繁荣。

val i = 1 -> Thread reads 1 -> Threads 2 reads 1 -> Thread 1 does 1 * .2 = 1.2 -> Thread 2 does 1 * .3 = 1.3 -> Thread 1 writes 1.2 back -> Thread 2 cooly overwrites it to 1.3 instead of doing 1.2 * .3