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



我正在阅读"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易变不会修复此代码。该方法需要synchronized

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

在没有明确指示的情况下,代码(和 CPU)将假定可以使用此本地缓存版本,并愉快地读取和更新缓存变量,而计划在不同内核上的另一个线程将愉快地使用自己的缓存版本执行相同的操作。

当使用并发原语如synchronizedvolatile时,这种情况会发生变化。对于synchronized块(简化),Java 实现将确保这些值在首次读取时从主内存中检索并在synchronized块结束时写回主内存,它对volatile变量执行相同的操作,但在每次读取和写入时。

实际上,事情要复杂一些,线程之间的发生前关系等。但基本上,您的问题归结为"责怪缓存"。

最新更新