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

问题描述 投票:3回答:4

我一直在经历JCIP,作者在那里说..

线程限制的特殊情况适用于volatile变量。只要确保volatile变量仅从单个线程写入,对共享的volatile变量执行读 - 修改 - 写操作是安全的。

例如,count ++被认为是一个复合操作(读取值,向其中添加一个值,并更新值)并将count指定为volatile不会使此操作成为原子操作,因此这里不保证线程安全!我对吗 ??但是在这里作者说我们可以修复它,如果我们确保volatile变量只从单个线程写入。我没理解这一点。请提供说明。

java multithreading volatile
4个回答
6
投票

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

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变量的唯一时间。这可以在用于停止线程的这种模式中看到:

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。无论有多少人这样做,无论顺序如何,结果总是一样的。


6
投票

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

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

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

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


0
投票

这背后的想法是,为了避免数据竞争,必须只有一个写入线程,然后通过在关系之前发生的Java内存模型来处理可见性保证。如果有多个写入线程,那么仅通过将变量声明为“volatile”就无法使隐式复合操作成为原子

17.4.4。同步顺序

对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中“后续”根据同步顺序定义)。


0
投票

我想你和我的here有类似的疑虑。虽然只有一个线程对volatile变量执行读 - 修改 - 写操作,但读取线程确实可以读取变量的“临时”值;但这并不意味着该程序不是线程安全的。它读取“临时”值,因为写入线程尚未将其写入内存。换句话说,写作和阅读线程“看到变量的相同状态” - 它们仍然完全同步。

© www.soinside.com 2019 - 2024. All rights reserved.