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

时间:2018-01-02 09:57:40

标签: java multithreading synchronization

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

private static int nextSerialNumber = 0;

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

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

  

更令人惊讶的是,一个线程可以调用   generateSerialNumber反复,获得一系列序列   数字从零到n,之后另一个线程调用   generateSerialNumber并获得零序号。没有   同步,第二个线程可能看不到所做的更新   由第一个。这是上述存储器模型的结果   问题。

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

2 个答案:

答案 0 :(得分:4)

  

因为是int,所以写作是原子的。

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

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

此外,nextSerialNumber++; 不是原子,因为它包含read-update-write个步骤,因此使nextSerialNumber volatile不会修复此代码。该方法必须为synchronized

答案 1 :(得分:2)

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

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

使用synchronizedvolatile等并发原语时,会发生变化。对于synchronized - 块(简化),Java实现将确保在首次读取时将从主内存中检索这些值,并在synchronized块结束时将其写回主内存,它会执行volatile个变量相同,但每次读写都是一样。

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