为什么在某些情况下,尽管进行了更新,但线程的更新仍然不可见?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么在某些情况下,尽管进行了更新,但线程的更新仍然不可见?相关的知识,希望对你有一定的参考价值。

我正在读“Effective Java”,在他谈论线程的章节中,我介入了这个片段:

private static int nextSerialNumber = 0;

public static int generateSerialNumber(){
    return nextSerialNumber++;
}

稍后,谈论该片段,但万一没有同步,他说:

更令人惊讶的是,一个线程可以重复调用generateSerialNumber,获得从0到n的序列号序列,之后另一个线程调用generateSerialNumber并获得零序列号。如果没有同步,第二个线程可能看不到第一个进行的更新。这是上述内存模型问题的结果。

我无法理解这是如何可能的。要使线程获得“从零到n的序列号序列”,必须完成增量,否则线程将始终读取相同的值。如果增量完成,则设置变量,因为作为int,写入是原子的。因此,如果静态变量被线程更改,虽然它可能是相同的,但另一个线程必须能够读取该值。那么调用generateSerialNumber的另一个线程怎么可能获得零序列号呢?

答案

因为作为一个int,写作是原子的。

这只是意味着另一个线程不可能看到一个半更新的值,它是旧的或新的(在64位系统中,它扩展到long变量)。

它与基本可见性问题无关,这意味着除非您的变量是volatile或者您正在使用synchronization,否则该值可以并且将由不同的线程缓存以用于性能目的,您将看到旧的缓存值而不是最新的值。

此外,nextSerialNumber++;不是原子的,因为它由read-update-write步骤组成,因此使nextSerialNumber volatile不会修复此代码。该方法需要是synchronized

另一答案

nextSerialNumber的当前值可能缓存在核心本地的缓存中,对该值的更新也可能会缓存一段时间,直到它们被刷新到主内存。因此,当使用在不同核心上安排的多个线程时,它们可能拥有自己的本地缓存版本的nextSerialNumber

如果没有明确指示,代码(和CPU)将认为可以使用这个本地缓存版本,并愉快地读取和更新缓存变量,而在另一个核心上安排的另一个线程将很乐意对其自己的缓存版本执行相同操作。

当使用synchronizedvolatile这样的并发原语时,这会发生变化。对于synchronized-block(简化),Java实现将确保在第一次读取时将从主存储器中检索这些值,并在synchronized块的末尾写回主存储器,它对volatile变量执行相同的操作,但随后在每次读写。

实际上事情有点复杂,在线程之间发生关系等等。但基本上,你的问题归结为“责备缓存”。

以上是关于为什么在某些情况下,尽管进行了更新,但线程的更新仍然不可见?的主要内容,如果未能解决你的问题,请参考以下文章

C ++线程:尽管没有种族,但共享内存未更新

Google Analytics(分析)报告停止更新,但实时视图仍显示活动

AS3 - 数组对象仍在更新?

在多线程中,子线程更新主线程ui都有哪些方法及注意点

PyQt:如何在不冻结 GUI 的情况下更新进度?

Firebase 按预期拒绝使用 PermissionDenied 进行更新,但侦听节点的客户端仍会收到发送的数据