我正在阅读“Effective Java”,在他谈论线程的章节中,我介入了这个片段:
private static int nextSerialNumber = 0;
public static int generateSerialNumber(){
return nextSerialNumber++;
}
稍后,谈论该片段,但万一没有同步,他说:
更令人惊讶的是,一个线程可以调用
generateSerialNumber
反复,获得一系列序列 数字从零到n,之后另一个线程调用generateSerialNumber
并获得零序号。没有 同步,第二个线程可能看不到所做的更新 由第一个。这是上述存储器模型的结果 问题。
我无法理解这是怎么回事。
要使线程获得“从零到n的序列号序列”,必须完成增量,否则线程将始终读取相同的值。如果增量完成,则设置变量,因为是int
,写入是原子的。
因此,如果静态变量被线程更改,虽然它可能是相同的,但另一个线程必须能够读取该值。那么调用generateSerialNumber
的另一个线程如何可以获得零序列号呢?
答案 0 :(得分:4)
因为是
int
,所以写作是原子的。
这意味着另一个线程无法看到半更新的值,它可以是旧的也可以是新的(在64位系统中,它扩展到long
个变量)。
它与基本可见性问题无关,这意味着除非您的变量为volatile
或您使用的是synchronization
,否则该值可以出于性能目的,由不同的线程缓存,您将看到旧的缓存值而不是最新的缓存值。
此外,nextSerialNumber++;
不是原子,因为它包含read-update-write
个步骤,因此使nextSerialNumber
volatile不会修复此代码。该方法必须为synchronized
。
答案 1 :(得分:2)
nextSerialNumber
的当前值可能缓存在核心本地的缓存中,对该值的更新也可能会缓存一段时间,直到它们被刷新到主内存。因此,当使用在不同核心上安排的多个线程时,它们可能拥有自己的nextSerialNumber
本地缓存版本。
当没有明确指示时,代码(和CPU)会认为使用这个本地缓存版本是好的,并且愉快地读取和更新缓存的变量,而在另一个核心上安排的另一个线程将很乐意对其执行相同的操作。自己的缓存版本。
使用synchronized
和volatile
等并发原语时,会发生变化。对于synchronized
- 块(简化),Java实现将确保在首次读取时将从主内存中检索这些值,并在synchronized
块结束时将其写回主内存,它会执行volatile
个变量相同,但每次读写都是一样。
实际上事情有点复杂,在线程之间的关系发生之前等等。但基本上,你的问题归结为"责备缓存"。