线程安全之可见性揭秘

线程安全之可见性问题

JVM运行时数据区和JMM(java内存模型)有什么区别?
1.  JVM运行时数据区  是 由 《==Java虚拟机规范==》定义的

    理解:不同的JVM厂商都必须 遵循 《==Java虚拟机规范==》进行开发

2.   JMM是由 《java语言规范》定义的

    理解:每一种语言都有自己的 解释器或编译器
什么是可见性问题?

我们先来看一段程序,如下:
在这里插入图片描述
大家猜猜这个程序运行结果是什么样子? 好了,不卖关子啦,我这里已经运行过了,如下图:
在这里插入图片描述
我们可以看到,【i】的值没有打印,而且线程还没有执行结束,那是为什么呢? 那我们一起来一步一步的分析推理下,我们先把JMM逻辑图画一下,如下图:
在这里插入图片描述

  1. main线程和子线程都读取了 主内存中 isRunning=true 对应图中的 (1)、(2)
  2. main线程更改主内存 isRunning = true
  3. 子线程没有立刻读取到 主内存变化的 值,导致线程可见性问题

接下来我们一起分析下:
导致可见性问题只有两种可能:

  • main线程没有将新值写入主内存;

  • main将新值成功写入了主内存,子线程没有读取到;

不管是哪一种,我们有理由猜测是由于在工作线程和主内存之间存在的 CPU高速缓存导致,是高速缓存的锅,那我们再分析下:
如果是高速缓存的锅,那为什么在main线程sleep了3秒(我们都知道CPU的高速缓存运行速度比内存要快的多)之后,子线程读到isRunning还是true呢?我们有理由猜测,不是CPU高速缓存的锅,那会是谁呢?
接来我们通过下图接着分析:
在这里插入图片描述
javac 是编译前只是将.java文件编译成.class字节码文件,而不是机器码,CPU不会执行代码,有理由猜测不是javac的锅,根据排除法,最后我们有理由锁定是JIT编译器的锅,那我们来看下JIT编译器做了什么事情:
通过云课堂学习,我们得知 JIT编译器在执行的时候会 遵循as-if-serial语义,会对代码进行指令重排(即优化提升性能),就如我们上图看到的JIT编译器代码的样子,到此真凶已经找到,那怎么解决呢?请看《可见性问题解决方案》

可见性问题解决方案

对多线程稍微有接触的童鞋都知道 用volatile关键字解决,那我们来揭开它的面纱看一下:

  • 我们用javap -v -p demo.class 反编译下 刚才执行的.class文件,如下图:
    在这里插入图片描述

我们看到 用volatile修饰的isRunning变量反编译后 看到访问控制加了个ACC_VOLATILE标识符,我们到Oracle官网看下这个标识符的解释:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5
在这里插入图片描述

我们看到 cannot be cached 翻译过来就是不能被缓存,这里指的就是不允许JIT编译器进行缓存,到这里volatile神秘面纱就揭开了,到此我们也整理下volatile关键字吧

  • 可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
  • java内存模型规定:
  • 对volatile变量v的写入,与所有其他线程后续对v的读同步
  • 要满足这些条件,所以volatile关键字就有这些功能:
  1. 禁止缓存: volatile变量的访问控制符会加个 ACC_VOLATILE
  2. 对volatile变量相关的指令不做重排序
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值