由于flag
变量被多个线程使用,因此必须使用某种机制来确保更改可见性。这确实是一般多线程中的常见模式。Java 内存模型不保证其他线程将永远看到flag
.
这是为了允许现代多处理器系统采用优化,在这些系统中,始终保持高速缓存一致性可能代价高昂。内存访问通常比其他“通常”的 CPU 操作慢几个数量级,因此现代处理器竭尽全力尽可能地避免它。相反,经常访问的位置保存在小型、快速的本地处理器内存中——缓存。更改仅对缓存进行,并刷新在某些点到主存储器。这适用于一个处理器,因为内存内容不会被其他方更改,因此我们保证缓存内容反映内存内容。(嗯,这是一个过度简化,但从高级编程的角度来看,我相信这无关紧要)。问题是,一旦我们添加另一个处理器,独立更改内存内容,这种保证就会丢失。为了缓解这个问题,设计了各种(有时是详尽的 - 参见例如这里)缓存一致性协议。不过,不出所料,它们需要一些簿记和处理器间通信开销。
其他一些相关的问题是写操作的原子性。基本上,即使更改被其他线程看到,也可能会部分看到。这在 java 中通常不是什么大问题,因为语言规范保证了所有写入的原子性。尽管如此,对 64 位原语 (long
和double
) 的写入被明确称为被视为两个独立的 32 位写入:
出于 Java 编程语言内存模型的目的,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:每个 32 位一半。这可能导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到后 32 位。( JLS 17.7 )
回到有问题的代码......需要同步,并且synchronized
块满足需要。不过,我发现制作这样的标志volatile
更令人愉快的解决方案。净效果是相同的——可见性保证和原子写入——但它不会用小块混淆代码synchronized
。