Java中的volatile
volatile的内存语义
内存可见性
volatile
是Java
虚拟机提供的轻量级的同步机制,它有3个特性:
1)保证可见性
2)不保证原子性
3)禁止指令重排
volatile
是Java
提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized
相比(synchronized
通常称为重量级锁),volatile
更轻量级,相比使用synchronized
所带来的庞大开销,倘若能恰当的合理的使用volatile
,自然是美事一桩。
1 |
|
上面这个例子,模拟在多线程环境里,t1线程对flag共享变量修改的值能否被t2可见,即是否输出 “—–flag被设置为false,程序停止” 这句话?
这个结论会让人有些疑惑,可以理解。因为倘若在单线程模型里,因为先行发生原则之happens-before,自然是可以正确保证输出的;但是在多线程模型中,是没法做这种保证的。因为对于共享变量flag来说,线程t1的修改,对于线程t2来讲,是”不可见”的。也就是说,线程t2此时可能无法观测到flage已被修改为false。那么什么是可见性呢?
所谓可见性,是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有的变量都存储在主内存中。很显然,上述的例子中是没有办法做到内存可见性的。
volatile的内存语义
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量。
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取,从而保证了可见性。
volatile变量有2大特点,分别是:
可见性
有序性:禁重排!
重排序是指编译器和处理器为了优化程序性能面对指令序列进行重新排序的一种手段,有时候会改变程序予以的先后顺序。
- 不存在数据依赖关系,可以重排序;
- 存在数据依赖关系,禁止重排序。
但重排后的指令绝对不能改变原有串行语义!
那么volatile凭什么可以保证可见性和有序性呢??
- 内存屏障Memory Barrier~
内存屏障在另外一篇文章里面.
volatile
有关禁重排的行为
- 当第一个操作为
volatile
读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile
读之后的操作不会被重排到volatile
读之前(volatile
读之后的操作,都禁止重排序到volatile
之前) - 当第二个操作为
volatile
写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile
写之前的操作不会被重排到volatile
写之后 (volatile
写之前的操作,都禁止重排序到volatile
之后) - 当第一个操作为
volatile
写时,第二个操作为volatile
读时,不能重排。(volatile
写之后volatile
读,禁止重排序的)
内存屏障四大指令插入情况
- 在每个
volatile
写操作的前面插入一个StoreStore
屏障,保证在volatile
写之前,其前面的所有普通写操作都已经刷新到主内存中。 - 在每个
volatile
写操作的后面插入一个StoreLoad
屏障,避免volatile
写与后面可能有的volatile
读/写操作重排序 - 在每个
volatile
读操作的后面插入一个LoadLoad
屏障,禁止处理器把上面的volatile
读与下面的普通读重排序。 - 在每个
volatile
读操作的后面插入一个LoadStore
屏障,禁止处理器把上面的volatile
读与下面的普通写重排序。