我从《Java Concurrency in Practice》这本书中了解到了volatile
的以下内容:
当一个变量声明为
volatile
时,编译器和运行时会知道该变量是共享的并且它的操作不应与其他内存操作重新排序。volatile
变量不会被缓存在寄存器或者其他处理器的缓存中,因此对volatile
变量的读取总是返回任何线程最新的写入值。
volatile
变量的可见性效果超出了volatile
变量本身的值范围。当线程A写入一个volatile
变量,并且随后线程B读取相同的变量时,A在写入volatile
变量之前所有可见的变量值也将在B读取volatile
变量之后变得可见。所以从内存可见性的角度来看,写入volatile
变量就像退出同步块,而读取volatile
变量就像进入同步块。
我对上面最后一句话很困惑。假设变量x
被定义为volatile
,并且在修改x
之前,变量u
、v
和w
对线程A可见,那么当线程B随后读取x
时,它也将能够读取变量u
、v
和w
的最新值。我们可以为synchronized
指定相同的内容吗?
Q1.也就是说,下面的理解是否正确?
在退出
synchronized
块之前,变量u
、v
和w
对线程A
可见,那么当线程B
随后进入synchronized
块时,它将能够读取变量u
、v
和w
的最新值。
我认为上述观点是不正确的,因为变量u
、v
和w
可能存储在缓存和寄存器中,因为它们没有被定义为volatile
。我的理解正确吗?所以synchronized
(以及locks
和atomic
变量,因为它们类似于synchronized
)不能保证可见性。
该书进一步指出:
锁定可以同时保证可见性和原子性;
volatile
变量只能保证可见性。
但我认为以下内容:
- 锁、
synchronized
和原子变量仅保证读取-写入原子性(不保证可见性和防止重排序)。 volatile
保证可见性,并通过编译器和运行时防止重排序(不保证读取-写入原子性)。
Q2.我的上述两个观点是否正确?
volatile
并不能使变量修改原子化,因为Java并发实践这本书中说到:“volatile
的语义不足以使增量操作(count++)原子化,除非你可以保证该变量只从单个线程中写入”。这是否意味着只有一个线程对volatile
变量进行读写时是原子的,因为volatile
确保不会对该线程进行重新排序,但是如果有其他线程修改同一变量,则不会是原子的,因为重新排序预防无法跨线程工作? - MsAi++
并不是单一的“访问”。它的意思是:获取i
的值,然后将递增的值存储回i
中。也就是说,这是两个“访问”的序列。 - Solomon Slowlong
和double
类型的volatile变量进行的写入和读取操作始终是原子性的。”但我之前说的话(现在已删除)仍然基本正确:无论您是否将它们声明为volatile
,对所有其他类型的变量进行的写入和读取操作都是原子的。只有在long
或double
这种特殊情况下,volatile
才会在原子性方面添加任何内容。 - Solomon Slow