Java代码经过Java编译器编译成字节码,并由类加载器加载到JVM运行时数据区,
最终由JVM执行引擎执行字节码,转换为汇编指令在CPU内执行。
Java并发机制主要依赖JVM实现和CPU指令。
volatile
volatile修饰变量,提供多处理器并发中共享变量的可见性,是轻量的synchronized。
volatile由CPU指令实现,不会引起上下文切换,性能比synchronized高。
可见性问题
可见性问题是受Java内存模型的影响。
从上图可知,多线程对共享变量在本地内存进行处理,虽然提高执行效率,但是将带来可见性和数据同步问题。
多线程编程中,对于volatile共享变量,一个线程进行了修改,对于其他线程能及时读取修改后的值,以做到资源同步。
volatile定义
如果一个变量被声明为volatile,那么该变量在多线程中将互相可见,保障准确和一致性。
内存屏障/内存栅栏
一系列CPU指令,控制内存操作的顺序,可以解决指令重排和可见性问题。
其中,指令重排是JIT对代码的优化。
内存屏障插入策略:
- 在volatile写前插入storestore,禁止上面普通写与volatile写重排
- 在volatile写后插入storeload,禁止下面volatile读/写与volatile写重排
- 在volatile读后插入loadload,禁止下面普通读与volatile写读重排
- 在volatile读后插入loadstore,禁止下面普通写与volatile读重排
volatile实现原理
在共享变量写前,CPU新增Lock操作指令,实现以下效果:
- 将当期缓存行回写系统内存
- 其他CPU缓存该内存地址数据无效
为提高处理速度,CPU不和主存直接交互,而是将主存数据读取到缓存,但缓存写回内存时间未知。
通过volatile将通过Lock指令实现写回操作,其他处理器通过总线检查缓存值是否过期;若发现
缓存行地址变更,则将缓存行置为无效。
当处理器处理数据时,发现无效标志则会从主存中重新加载数据至缓存。
说明
- Lock前缀指令会引起处理器缓存写回内存。Lock可以锁定缓存区域,实现原子操作。
- 处理器缓存写回操作将引起其他处理的该缓存无效。处理器嗅探缓存地址,若处于共享状态,无效缓存行后,下次访问进行强制缓存行填充。
volatile优化
JDK7追加64字节能够提高LinkedTransferQueue并发编程的效率
缓存的最小单位是缓存行,常见CPU的缓存行是64字节宽(8字节),不支持部分缓存行。
不满64字节,则缓存队列头和尾至同一缓存行。
当CPU修改头节点,需要锁定缓存行,导致其他CPU不能处理缓存,影响队列入队和出队效率。
追加至64位,填满缓存行,避免队列的头和尾节点被同一缓存行同时锁定。
volatile使用
1 | public class VolatileTest { |
结果频繁出现:不一致
若不使用volatile,因为线程执行速度快,偶尔会出现不一致
但不频繁
说明volatile可以实时同步各线程共享变量
volatile修饰用法
- volatile 基本变量int\boolean等,例如volatile int a=1;保障可见性
- volatile 对象,例如volatile Singleton singleton;防止指令重排
注意
- volatile保障变量在线程间的可见性,但不保障操作原子性