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

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

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

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

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

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

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

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

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

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

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

17.4.4。 同步顺序

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

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