简介volatile 是一种同步机制,比 synchronized 或 Lock 相关类更轻量,因此使用 volatile 并不会发生上下文切换等开销很大的行为 。
如果一个变量被修饰成 volatile,那么 JVM 就知道了这个变量可能会被并发修改 。
因为其开销小,所以对应的功能也小,volatile 不能像 synchronized 一样提供原子保护 。
实现原理Java语言规范第3版中对volatile的定义如下:Java编程语言允许线 程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应 该确保通过排他锁单独获得这个变量 。Java语言提供了volatile,在某些 情况下比锁要更加方便 。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的 。
volatile 是如何保证可见性的?首先通过工具获取 JIT 编译器生成的汇编指令来查看对volatile进行写操作时,CPU会 做什么事情 。
java 代码:
instance = new Singleton(); // instance是volatile变量转变成汇编代码,如下:
0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇 编代码,通过查IA-32架构软件开发者手册可知,Lock 前缀的指令在多核处理器下会引发了两件事情。
- 将当前处理器缓存行的数据写回到系统内存 。
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数 据无效 。
volatile 的两条实现原则1)Lock前缀指令会引起处理器缓存回写到内存;2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效 ;主要特性可见性代码实践
public class MyData {// 这里去掉 volatile 后 main 线程就发阻塞volatile int num = 0;public void addNum() {num = 60;}}class VolatileDome{public static void main(String[] args) {MyData myData = https://tazarkount.com/read/new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addNum();System.out.println(Thread.currentThread().getName() + myData.num);},"AAA").start();while (myData.num == 0) {}System.out.println(Thread.currentThread().getName() + "mission is over");}}不保证原子性public class MyData {volatile int num = 0;public void addNumPlus() {num++;}}class VolatileDome{public static void main(String[] args) {MyData myData = https://tazarkount.com/read/new MyData();for (int i = 0; i < 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {myData.addNumPlus();}}, String.valueOf(i)).start();}while (Thread.activeCount() > 2) {Thread.yield();}// 最终打印的结果很大可能不是 20W 。System.out.println(myData.num);}}addNumPlus 字节码解读 0 aload_0 1 dup 2 getfield #2 <com/lhn/demo1/jmm/MyData.num : I> 5 iconst_1 6 iadd 7 putfield #2 <com/lhn/demo1/jmm/MyData.num : I>10 return通过字节码可以看出 n++ 被拆成了三个指令:- 执行 getfieid 拿到原始 n;
- 执行 iadd 进行加 1 操作;
- 执行 putfieid 把累加后的值写回;
解决原子性问题使用 synchronized(重量级操作,不推荐)
public synchronized void addNumPlus() {num++;}使用 atomic (推荐使用)AtomicInteger atomicInteger = new AtomicInteger();public void addAtomic() {atomicInteger.getAndIncrement();}禁止指令重排序计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分一下三种:graph LRA[源代码] --> B[编译器优化的重排] --> c[指令并行的重排] --> d[内存系统的重排] --> e[最终执行的指令]
单线程环境下确保程序最终执行结果和代码顺序执行的结果一样,处理器在进行重排序时必须要考虑指令之间的
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
