如何使 volatile count++ 操作成为线程安全的

how does the volatile count++ operation be made thread safe

我一直在浏览 JCIP,作者在那里说..

A special case of thread confinement applies to volatile variables. It is safe to perform read-modify-write operations on shared volatile variables as long as you ensure that the volatile variable is only written from a single thread

例如 count++ 被认为是一个复合操作(读取值、加一和更新值)并且将 count 声明为 volatile 不会使该操作成为原子操作,因此这里不保证线程安全!!我对吗 ??但是作者在这里说,如果我们确保volatile变量只从一个线程写入,我们就可以修复它。 我不明白这一点。请提供插图。

count++本质上是两个操作,读取count的值和store count + 1。

如果两个线程同时尝试执行 count++,它们可能都读取 count 的旧值并将其递增 1。因此,最后,如果您不添加任何同步机制,您最终的最终值将是 count + 1,而不是 count + 2。

Volatile 不保证这种场景下的线程安全(不是原子的),它只保证最近存储的值对所有线程可用。但这在上述场景中并没有多大帮助。您需要确保在存储新值之前,没有其他线程会读取计数的旧值。

如果您需要线程安全的计数器,我建议您查看 AtomicInteger(和类似的)类。他们提供了 count++ 的原子版本; incrementAndGet 方法。

典型的用例可能如下所示:

public class Foo implements Runnable {
    private volatile int itemsProcessed;

    public int getItemsProcessed() { return itemsProcessed; }

    @Override
    public void run() {
        while (true) { //this is just an example so we don't care about stopping
            processItem(); //process a single item
            itemsProcessed++;
        }
    }
}

现在其他人无需额外同步即可查询您线程的进度,但只允许线程本身更新它。对于其他人,该字段是只读的。

如果没有 volatile,其他线程可能根本看不到 itemProcessed 的任何变化,甚至可能会以古怪的顺序看到它们,有时增加,有时减少。


唯一可以让多个线程写入一个 volatile 变量而无需任何额外同步的情况是写入 idempotent(即,多个写入具有与单个效果相同)。这可以在用于停止线程的模式中看到:

public class Foo implements Runnable {
    private volatile boolean stopped = false;

    public void stopProcessing() {
        stopped = true;
    }

    public int getItemsProcessed() { return itemsProcessed; }

    @Override
    public void run() {
        while (!stopped) { 
            processItem(); //process a single item
        }
    }
}

这里其他线程唯一能做的就是将 stopped 设置为 true。不管有多少人做,以什么顺序做,结果总是一样的。

这背后的想法是,为了避免数据竞争,必须只有一个写入线程,然后可见性保证由 Java 内存模型通过 happens-before- 处理关系。如果有多个写入线程,则无法通过将变量声明为 "volatile"

来使隐式复合操作成为原子操作

17.4.4. Synchronization Order

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

我猜你和我有类似的疑虑here。虽然只有一个线程对 volatile 变量执行读取-修改-写入操作,但读取线程确实可以读取变量的 "temporary" 值;但这并不意味着该程序不是线程安全的。它读取 "temporary" 值,因为写入线程尚未将其写入内存。换句话说,写入线程和读取线程是 "seeing the same state of the variable" -- 它们仍然完全同步。