volatile引发的一系列血案
最早读《深入理解java虚拟机》对于volatile部分就没有读明白,最近重新拿来研究并记录一些理解
理解volatile前需要把以下这些概念或内容理解:
1、JMM内存模型
2、并发编程的三问题:原子性、一致性、有序性
3、先行发生原则
然后我们结合上面的几个知识点来看volatile如何使用
JMM内存模型
先看一下上面这张图片,即Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存
那么JMM为何要如此设计?其主要原因有两点:1、达到各平台访问内存效果的一致性 2、提升数据访问速度
对于提升数据访问速度,主要用到了CPU高速缓存这部分内容:计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存
在本文中,JMM能够帮助我们理解为什么会发生可见性问题
并发编程的三问题:原子性、可见性、有序性
原子性问题
原子性指:一个操作执行时不能被打断或插入
比如i++,JVM指令包括3个操作:读取x的值,进行加1操作,写入新的值,如果并发执行i++,可能这三步操作不同线程会穿插执行,原子性就是指,任何一个线程运行这三个操作时,其他线程不能进入运行这三步操作
如何解决原子性问题:
1、synchronized 2、Lock、其他锁
可见性问题
每个线程都有各自的工作内存(高速缓存、详见JMM),线程A更改了变量的值后,线程B从自己的工作内存中获取变量的值还可能是A修改前的值
如何解决可见性问题:
1、volatile关键字 2、Lock、synchronized
有序性问题
先看下什么是指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。如果程序不满足先行发生原则,那么可能发生指令重排
指令重排就影响了程序的有序性
如何解决有序性问题
1、volatile关键字 2、Lock、synchronized
从上面的三个问题来看volatile只能解决:可见性问题、有序性问题,但无法解决原子性问题,原子性问题仍需要锁的手段才能解决
先行发生原则 Happens-Before
先行发生原则(Happens-Before)是判断数据是否存在竞争、线程是否安全的主要依据,先行发生原则,可以帮你判定是否并发安全的,从而不必去猜测是否是线程安全了
下面是Java内存模型中一些“天然的”先行发生关系,这些先行发生关系无需任何同步协助器协作java自带这些规则,可以直接在编码中使用。如果两个关系不在此列,而又无法通过这些关系推导出来,它们的顺序就无法保证,虚拟机可以对它们任意重排序
程序次序规则: 同一个线程内,按照代码出现的顺序,前面的代码 happens-before 后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。
管程锁定规则: 对于一个监视器锁的unLock操作 happens-before 于每个后续对同一监视器锁的Lock操作。
volatile变量规则: 对volatile域的写入操作 happens-before 于每个后续对同一个域的读操作。
线程启动规则: 在同一个线程里,对Thread.start的调用会 happens-before 于每一个启动线程中的动作。
线程终结规则: 线程中的所有动作都 happens-before 于其它线程检测到这个线程已经终结,或者从Thread.join()调用成功返回,或者Thread.isAlive返回false.
中断规则: 一个线程调用另一个线程的interrupt happens-before 于被中断的线程发现中断(通过抛出InterruptedException 或者调用isInterrupted和interrupted)
终结规则: 一个对象的构造函数的结束 happens-before 于这个对象finalizer的开始
传递性: 如果 A happens-before 于 B,且 B happens-before 于 C,则 A happens-before 于C。
其中比较重要且难以理解的几条是:
程序次序规则
一段程序代码的执行在单个线程中看起来是有序的。虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
管程锁定规则
一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作,也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作
volatile变量规则
对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作,如果线程1写入了volatile变量v,接着线程2读取了v,那么,线程1写入v及之前的写操作都对线程2可见(线程1和线程2可以是同一个线程),可以看成是volatile解决可见性问题的描述
总结下来就是先行发生原则可以确定两件事:
1、能帮助我们判断程序是否线程安全
2、能帮助我们确定程序是否可能发生指令重排
使用volatile
有了以上知识储备我们来看一下volatile如何正确的使用
1、多读单写
只有一个线程控制改变volitile变量的值,一个或多个线程并发读取volitile变量的值都可以用volitile
通常:线程开关或者状态标记的场景可以使用
因为可见性保证了volatile多读单写的能力,但又因为volatile没有解决原子性问题的能力,所以不是多读多写
public static volatile boolean flag = false;
//这种情况不添加volatile就有可能造成无法退出程序了
//添加了volatile就强制从主内存中获得值,就不会出现上述问题了
//这个例子体现:只有一个线程控制改变volatile变量的值 很多线程并发读取volitile变量的值都可以用volatile
new Thread(() -> {
while(!flag){
}
System.out.println("退出了");
}).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("setup");
flag = true;
//特别说明:我测试flag是非volatile,当不在while(!flag){上sleep,会一直循环,这种非常可能拿不到更改后的值,一直从工作内容中获得缓存值false。
2、防止指令重排
防止指令重排,通常:单例懒汉模式 double-check中使用
public class LazySingleton {
private volatile static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(lazySingleton == null){
synchronized (LazySingletonill.class){
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
public static void main(String[] args){
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(LazySingleton.getInstance().hashCode());
}).start();
}
}
}
我们来看一下为什么不加volatile会引发指令重排的问题:
首先,这个出现问题的概率并不高,并且我通过jdk8的版本反编译并未和帖子内容一致,姑且先把帖子的原理写一下:
instance = new LazySingleton();,其实JVM内部已经转换为多条指令:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址但是经过指令重排序后如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象2、3步骤指令重排后发生了交换
假如线程A获得了锁并且正在执行lazySingleton = new LazySingleton();,这个实例化的jvm指令发生了重排,即instance = memory先于ctorInstance(memory)执行,刚好instance = memory执行完毕,线程B登场在执行if(lazySingleton == null){时为false,线程B return了一个没有初始化对象的实例出去,出现了返回不正确结果的现象
volatile引发的一系列血案的更多相关文章
- DataSet筛选数据然后添加到新的DataSet中引发的一系列血案
直入代码: var ds2 = new DataSet(); ) { ].Select(" usertype <> 'UU'"); ) { DataTable tmp ...
- 在centos服务器上配置gitlab钩子引发的一系列问题
为了给公司的服务器上搭建gitlab环境并且配置钩子(实现在本地git push之后服务器自动git pull),整了好久,最后终于把问题解决了,下面是记录安装gitlab之后引发的一系列问题: 首先 ...
- iOS回顾笔记( 02 ) -- 由九宫格布局引发的一系列“惨案”
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- Dynamics CRM中一个查找字段引发的【血案】
摘要: 本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复267或者20180311可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyon ...
- Feign 400错误引发的一系列问题
Feign 400错误引发的一系列问题 问题介绍 在使用Feign进行远程调用的时候出现非常奇怪的400错误,错误信息大概如下: feign.FeignException: status 400 re ...
- 记一次全站升级https引发的一系列问题
中秋假期,闲来无事.花了一下午折腾了下https,说实话这年头还有网站不上https显然是折腾精神不够啊~ 1.SSL证书评估 看了市面上各种类型的证书,有收费的也有免费的,但是最终还是选择了腾讯云提 ...
- break使用不当引发的一个“血案”
最近在网上冲浪,读到一则新闻,摘抄下这则新闻: ======================= 以下文字摘抄自互联网==================== 1990年1月15日,AT&T电话 ...
- 未关闭虚拟机直接关闭vmware引发的一系列问题——Windows下linux虚拟机
虚拟机长时间挂起重新打开时卡顿,无法开启,脑抽直接关闭了vmware软件引起的一系列问题. 原因是关闭了vmware,但是相应的虚拟机并没有关闭,所以虚拟机不能重开 会出现如下提示 解决方案如下: 1 ...
- 配置进程外Session 同时解决一个奇怪的BUG 因为SQLserver 服务器名不是默认的.或者localhost而引发的一系列问题
用公司的电脑学习如鹏网的视频,开发一个项目,用到了进程外session,因为公司电脑SQLServer 是2008 服务器名称是. 然后参考这篇文章进行设置进程外session 很顺利 完成了设置. ...
随机推荐
- unity5 manifest
https://www.cnblogs.com/lancidie/p/5878789.html 之前曾经写了一篇博客介绍Unity5的AssetBundle,结果似乎很受关注.不过似乎很多人看了之后都 ...
- ue4 svn备份目录
http://blog.csdn.net/sh15285118586/article/details/55737480 UE4工程文件备份目录有:Config.Content.Plugins.Sour ...
- 3dmax切割平行线
1 选择物体(可编辑多边形),选择边 ,然后点击切片平面 2 然后会出现黄色线框 3 移动旋转黄色线框到合适位置,然后点切片 4 结果
- Ubuntu下安装wine plsql
在电脑上安装了第二系统Ubuntu,但面临各种Linux不支持的开发软件也是束手无策.比如常用的Eclipse,PlSQl,Oracle,QQ等等,于是,上网查阅各种资料,最终的解决方案还是要依赖于w ...
- 微信小程序采坑之上拉触底加载更多和下拉刷新
小程序中加载更多数据一般都是触底刷新 有自带的函数: onReachBottom: function (){} 但是在使用时触发完全没有反应,后来尝试给外层加了一个高度,解决问题 仔细想想也是,没有设 ...
- 字符串前面u,r,b
u :代表是对字符串进行unicode编码. 一般英文字符在使用各种编码下, 基本都可以正常解析, 所以一般不带u:py3当对字符串进行操作的时候,默认使用Unicode编码 r/R:非转义的原始字符 ...
- 译—— a tale of viewport2
这一页我们将讨论移动浏览器.如果您对移动设备完全陌生,我建议您首先阅读第一部分关于桌面浏览器的内容,以便在熟悉的环境中做好准备. 移动浏览器的问题 移动浏览器和桌面浏览器比较,最明显的差异是屏幕大小. ...
- idea 添加yuicompressor压缩js/css
打开idea 点击file->Settings 出现如下界面 argumets项填写 : -jar F:\yui\yuicompressor-2.4.8.jar $FilePath$ -o $F ...
- atcoder square869120Contest#3 F 寿司
省选round1的时候dalao的推荐——atcoder的题目码量不大,但很巧妙,题目比较难找,挂个链冷静一下:http://s8pc-3.contest.atcoder.jp/tasks/s8pc_ ...
- netty~引用对象引用
从InBound里读取的ByteBuf要手动释放,还有自己创建的ByteBuf要自己负责释放.这两处要调用这个release方法. write Bytebuf到OutBound时由netty负责释放, ...