title: java-volatile
date: 2022-04-21 14:13:25
tags:
- 学习
- 转载与汇总
- java并发
来自我的博客 :ZWN的Blog,https://zwn2001.github.io/2022/04/21/java-volatile/
关于并发关键字Volatile
volatile
通常被比喻成"轻量级的synchronized
",也是Java并发编程中比较重要的一个关键字。和synchronized
不同,volatile
是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
Java
的volatile
关键字用于标记一个变量 “应当存储在主存” 。更确切地说,每次读取volatile
变量,都应该从主存读取,而不是从CPU缓存读取。每次写入一个volatile
变量,应该写到主存中,而不是仅仅写到CPU缓存。
实际上,从Java 5开始,volatile
关键字除了保证volatile
变量从主存读写外,还提供了更多的保障。
变量可见性问题
Java
的volatile
关键字能保证变量修改后,对各个线程是可见的。这个听起来有些抽象,下面就详细说明。
在一个多线程的应用中,线程在操作非volatile变量时,出于性能考虑,每个线程可能会将变量从主存拷贝到CPU缓存中。如果你的计算机有多个CPU,每个线程可能会在不同的CPU中运行。这意味着,每个线程都有可能会把变量拷贝到各自CPU的缓存中,如下图所示:
对于非volatile
变量,JVM并不保证会从主存中读取数据到CPU缓存,或者将CPU缓存中的数据写到主存中。这会引起一些问题。试想一下,如果有两个以上的线程访问一个共享对象,这个共享对象包含一个counter
变量,下面是代码示例:
public class SharedObject {
public int counter = 0;
}
如果只有线程1修改了(自增)counter
变量,而线程1和线程2两个线程都会在某些时刻读取counter
变量。
如果counter
变量没有声明成volatile
,则counter
的值不保证会从CPU缓存写回到主存中。也就是说,CPU缓存和主存中的counter
变量值并不一致,如下图所示:
这就是“可见性”问题,线程看不到变量最新的值,因为其他线程还没有将变量值从CPU缓存写回到主存。一个线程中的修改对另外的线程是不可见的。这其实是非常常见的一类问题。
volatile可见性保证
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
Java
的volatile
关键字就是设计用来解决变量可见性问题。将counter
变量声明为volatile
,则在写入counter
变量时,也会同时将变量值写入到主存中。同样的,在读取counter
变量值时,也会直接从主存中读取。
下面的代码演示了如果将counter
声明为volatile
:
public class SharedObject {
public volatile int counter = 0;
}
将一个变量声明为volatile
,可以保证变量写入时对其他线程的可见。
在上面的场景中,一个线程(T1)修改了counter
,另一个线程(T2)读取了counter(
但没有修改它),将counter
变量声明为volatile
,就能保证写入counter
变量后,对T2是可见的。
然而,如果T1和T2都修改了counter
的值,只是将counter
声明为volatile
还远远不够,后面会有更多的说明。
完整的volatile可见性保证
实际上,volatile
的可见性保证并不是只对于volatile
变量本身那么简单。可见性保证遵循以下规则:
- 如果线程A写入一个
volatile
变量,线程B随后读取了同样的volatile
变量,则线程A在写入volatile
变量之前的所有可见的变量值,在线程B读取volatile
变量后也同样是可见的。 - 如果线程A读取一个
volatile
变量,那么线程A中所有可见的变量也会同样从主存重新读取。
下面用一段代码来示例说明:
public