Volatile 能够保证可见性。
volatile的两大特性:禁止重排序、内存可见性。
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程来说是立即得知的。
2)禁止进行指令重排序。
并发编程中三大概念:原子性,有序性,可见性。
1.原子性
一个操作或者多个操作,要么全部执行并且执行过程中不会被任何一个因素打扰,要么就不会执行。
Java中,对于基本数据类型的变量读取和赋值操作是原子的。
1 | x=10; //1 |
只有1是原子的操作。
2是两个操作,先读取x的值,在把x的值写入工作内存,这两个操作都是原子操作,放在一起就不是原子操作了。
3和4都是先读取x的值,在进行加一的操作,写入新的值。
只有简单的读取、赋值(变量之间赋值不是)才是原子操作。
可以通过synchronized和Lock解决原子性问题。
2.可见性
线程之间的可见性,一个线程修改共享变量的值,其他的线程能够得知这个修改。
Java中,volatile关键字来保证可见性。
被volatile修饰的变量,变量修改的值会立即更新到主内存,每次使用前立即从主内存刷新,当其他线程需要读取的时候。就会读取主存的值。
普通变量被修改的时候,什么时候写入主存是不确定的。当其他线程读取的时候可能还是原来的值,因此无法保证可见性。
synchronized和final能实现可见性
同步块:对一个变量执行unLock之前,必须把此变量同步回主内存中。
final:被final修饰的字段在构造器中一旦初始化完成, 并且构造器没有把“this”引用传递出去,其他线程就能看见这个final字段的值。
3.有序性
程序执行的顺序按照代码先后执行。
指令重排序不会影响单个线程的执行,会影响到线程并发执行的正确性。
Java中允许编译器和处理器对指令进行重排序,volatile、和synchronized都可以保证有序性,
应用场景
1.状态标记量
2.double check
### volatile实现原理
happen-before (先行发生原则):判断数据是否存在竞争,线程是否安全的主要依据。
如果操作A先行发生于B,发生操作B之前,操作A产生的影响能被B观察到。
内存间相互操作
lock:主内存,把变量标识为一条线程独占状态
unlock:主内存,把一个处于锁定状态的变量释放出来,变量才可以被其他线程锁定
read:主内存,把一个变量从主内存传输到线程的工作内存,以便之后的load操作使用
load:工作内存,把 read操作从主内存中得到的变量放入工作内存的变量副本中
use:工作内存,把工作内存的变量值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时,将会执行此操作
assign:工作内存,把一个执行引擎护接收到的值赋值给工作内存中变量,每当虚拟机遇到一个需要给变量赋值的指令时执行此操作
store:工作内存,把工作内存中的一个变量值传送到主内存,以便之后的 write使用
write:主内存,把store操作在从工作内存得到的变量值放入主内存中
如果要把一个变量的从主内存复制到工作内存【read load】
如果要把一个变量从工作内存同步到主内存【store write】
java内存模型只要求上述两个操作必须按顺序执行
这8个操作必须满足的规则
不允许read和load、store和write单独使用
不允许一个线程丢弃assign
不允许一个线程无原因的(未发生过任何assign)把数据从线程的工作内存同步主内存
一个新变量之能从 主内存诞生,不允许在工作线程中直接使用一个未被初始化(load,assign)的变量
一个变量同一时刻只允许一条线程对其进行lock操作,lock可以被同一个线程重复多次执行,多次lock,只有执行相同次数的unlock,变量才会被解锁
如果对一个变量执行lock操作,将清空工作内存中此变量的值,在执行引擎使用这个变量时,需要重新执行 load 或assign
如果没有执行过lock,不允许对它执行unlock,也不允许去unlock一个被其他线程锁住的对象
进行unlock之前,必须将此变量 同步主存(store,write)
Java 内存模型对Volatile定义的特殊规则
定义变量V、W被volatile修饰,线程T会操作变量V和W。下面用浅显的语言解释Java内存模型对其的特殊规则:
- 每次使用前从主内存读取
read、load、use必须顺序整体出现。前一个操作是load时才能use,后一个操作时use时才能load。 - 每次修改后立即同步回主内存
assign、store、write必须顺序整体出现。前一个操作是assign时才能store,后一个操作时store时才能assign。 避免指令重排序
如果T对V的use或者assign先于T对W的use或者assign,那么T对V的load或者write必须先于T对W的load或者assign。加了volatile关键字的代码生成的汇编代码发现,会多出一个lock前缀指令。Lock指令对Intel平台的CPU,早期是锁总线,这样代价太高了,后面提出了缓存一致性协议,MESI,来保证了多核之间数据不一致性问题。
volatile读操作性能消耗和普通变量几乎没差别,写操作可能会慢一些,因为它需要在本地代码中插入许多内存屏障指令保证处理器不会发生乱序执行。
大多数场景下volatile总开销要比锁低。