死磕Java并发:Java内存模型之happens-before
本文转载自公众号: Java技术驿站
在上篇《死磕Java并发:深入分析volatile的实现原理》中提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题。那么我们正确使用同步、锁的情况下,线程A修改了变量a何时对线程B可见?
我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性。
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
happens-before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,我们解决在并发环境下两操作之间是否可能存在冲突的所有问题。下面我们就一个简单的例子稍微了解下happens-before ;
i = 1; //线程A执行j = i ; //线程B执行
j 是否等于1呢?假定线程A的操作(i = 1)happens-before线程B的操作(j = i),那么可以确定线程B执行后j = 1 一定成立,如果他们不存在happens-before原则,那么j = 1 不一定成立。这就是happens-before原则的威力。
happens-before原则定义如下:
1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
下面是happens-before原则规则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
我们来详细看看上面每条规则(摘自《深入理解Java虚拟机第12章》):
程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。
锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。
传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
上面八条是原生Java满足Happens-before关系的规则,但是我们可以对他们进行推导出其他满足happens-before的规则:
将一个元素放入一个线程安全的队列的操作Happens-Before从队列中取出这个元素的操作
将一个元素放入一个线程安全容器的操作Happens-Before从容器中取出这个元素的操作
在CountDownLatch上的倒数操作Happens-Before CountDownLatch#await()操作
释放Semaphore许可的操作Happens-Before获得许可操作
Future表示的任务的所有操作Happens-Before Future#get()操作
向Executor提交一个Runnable或Callable的操作Happens-Before任务开始执行操作
这里再说一遍happens-before的概念:如果两个操作不存在上述(前面8条 + 后面6条)任一一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。
下面就用一个简单的例子来描述下happens-before原则:
private int i = 0;public void write(int j ){ i = j;}public int read(){ return i;}
我们约定线程A执行write(),线程B执行read(),且线程A优先于线程B执行,那么线程B获得结果是什么?;我们就这段简单的代码一次分析happens-before的规则(规则5、6、7、8 + 推导的6条可以忽略,因为他们和这段代码毫无关系):
由于两个方法是由不同的线程调用,所以肯定不满足程序次序规则;
两个方法都没有使用锁,所以不满足锁定规则;
变量i不是用volatile修饰的,所以volatile变量规则不满足;
传递规则肯定不满足;
所以我们无法通过happens-before原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B指定,但是就是无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。那么怎么修复这段代码呢?满足规则2、3任一即可。
happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。
下图是happens-before与JMM的关系图(摘自《Java并发编程的艺术》)
参考资料:
周志明:《深入理解Java虚拟机》
方腾飞:《Java并发编程的艺术》
Java中synchronized的实现原理与应用)
- END -
往期推荐:
死磕Java系列:
深入分析ThreadLocal
深入分析synchronized的实现原理
深入分析volatile的实现原理
……
Spring系列:
Spring Cloud Zuul中使用Swagger汇总API接口文档
Spring Cloud Config Server迁移节点或容器化带来的问题
Spring Cloud Config对特殊字符加密的处理
Spring Boot使用@Async实现异步调用:使用Future以及定义超时
Spring Cloud构建微服务架构:分布式配置中心(加密解密)
Spring Boot快速开发利器:Spring Boot CLI
……
号外!
最近在做几个有意思的开源项目,感兴趣的朋友可以看看。
地址:
https://github.com/dyc87112/swagger-butler
可关注我的公众号
深入交流、更多福利
扫码加入我的知识星球
点击“阅读原文”,看本号其他精彩内容
死磕Java并发:Java内存模型之happens-before相关推荐
- java并发编程--内存模型
1.内存模型 1.1.高速缓存 程序运行过程中,将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行运算就可以直接从高速缓存中读取数据和写入数据,当运算结束之后将高速缓存中的数据刷新到 ...
- Java并发-JMM内存模型
1.线程与JVM 2.JVM内存模型与Java内存模型的区别 3.硬件内存架构与Java内存模型 4.Java内存模型对并发特征的保证 一.基本概念 程序:代码,完成某一件任务,代码序列(静态的概念) ...
- java关键字 valotile_Java内存模型-jsr133规范介绍,java中volatile关键字的含义
最近在看<深入理解Java虚拟机:JVM高级特性与最佳实践>讲到了线程相关的细节知识,里面讲述了关于java内存模型,也就是jsr 133定义的规范. 系统的看了jsr 133规范的前面几 ...
- 学习笔记【Java 虚拟机④】内存模型
若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 总目录 学习笔记[Java 虚拟机①]内存结构 学习笔记[Java 虚拟机②]垃圾回收 学习笔记[Java ...
- java happens before_java内存模型以及happens-before规则
原创文章&经验总结&从校招到A厂一路阳光一路沧桑 image# 1. JMM的介绍 # 在上一篇文章中总结了线程的状态转换和一些基本操作,对多线程已经有一点基本的认识了,如果多线程编程 ...
- JAVA基础驿站,Java基础:内存模型
在java基础当中,有很重要一块的内容,就是关于java当中的内存模型,所涉及的问题无非就是关于java当中的类.实例.变量.常量.静态变量.方法等blabla的东西都分别存储在什么地方.这部分的内容 ...
- 死磕 18 个 Java 8 的日期处理,工作必用!
死磕 18 个 Java 8 的日期处理,工作必用! 简介 关键类 实战 1. 获取当前的日期 2. 获取年.月.日信息 3.处理特定日期 4.判断两个日期是否相等 5.检查像生日这种周期性事件 6. ...
- JVM:高效并发机制——内存模型、线程、锁
为什么要进行并发(多任务处理) 处理器能力很强,尽可能压榨其劳动力 处理器处理速度与存储和通信速度相差甚大(IO.网络通信.数据库访问非常耗时),使得处理器总处在等待的状态,浪费计算资源 物理机提高处 ...
- Golang面向并发的内存模型
Golang面向并发的内存模型 在早期,CPU都是以单核的形式顺序执行机器指令.Go语言的祖先C语言正是这种顺序编程语言的代表.顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有 ...
- 【笔记】【一文搞定】java - 多线程:内存模型、生命周期、方法/关键词、并发问题、线程池、案例
参考: Java内存模型(JMM)详解 - https://zhuanlan.zhihu.com/p/518758482 线程安全性详解(原子性.可见性.有序性) - https://blog.csd ...
最新文章
- Glide 加载图片不显示(Android9.0无法加载图片)
- phalcon php 5.4,如何在php上安装phalcon
- Linux网卡驱动程序编写
- 学界 | 邢波团队提出 contrast-GAN:实现生成式语义处理
- TensorFlow2020:如何使用Tensorflow.js执行计算机视觉应用程序?
- 第二次冲刺------第三天
- Kafka#4:存储设计 分布式设计 源码分析
- 计算机二级目录设置,word2设置标题格式,生成目录,奇偶页设置等等,适用考计算机二级办公软件,也适用于毕业论文格式设置...
- mysql使用手册_mysql使用手册
- springmvc 项目完整示例05 日志 --log4j整合 配置 log4j属性设置 log4j 配置文件 log4j应用...
- 女生学计算机专业报考浙大,2021浙大适合女生的最好的专业
- 如何使用Aiseesoft iPhone Ringtone Maker for Mac在Mac上制作铃声
- 不宜佩带佛像,宜佩带佛号、咒语
- python格式化JSON结果打印
- Windows+Caffe+VS2013+python接口配置过程
- python实现离职滤波_如何用Python预测员工离职率?
- 尚硅谷新版Web前端HTML5+CSS3知识点集合篇【p1-p8】
- UE4_关于Roll,Yaw,Pitch,Rotator的理解
- elasticsearch forcemerge
- http协议1.0和1.1的区别
热门文章
- linux shell seq 序列生成命令 可用于for循环 简介
- clion 带参数调试运行
- linux c send recv MSG_NOSIGNAL参数
- linux 内核编译 Kconfig文件详解
- 2018几大主流的 UI/JS 前端框架
- C语言项目--教师信息/学生成绩管理系统
- 简单分析一下socket中的bind
- Linux System Programming --Chapter Two
- Python学习之猜数字小游戏
- 伏安特性实验误差分析_人教高中物理必修第三册《3 实验:电池电动势和内阻的测量》...