《深入理解 Java 内存模型》读书笔记(下)(干货,万字长文)
0. 前提
1. 基础
2. 重排序
3. 顺序一致性
4. Volatile
5. 锁
6. final
7. 总结
4. Volatile
4.1 VOLATILE 特性
举个例子:
public class VolatileTest {volatile long a = 1L; // 使用 volatile 声明 64 位的 long 型public void set(long l) {a = l; //单个 volatile 变量的写}public long get() {return a; //单个 volatile 变量的读}public void getAndIncreament() {a++; // 复合(多个) volatile 变量的读 /写}
}
假设有多个线程分别调用上面程序的三个方法,这个程序在语义上和下面程序等价:
public class VolatileTest {long a = 1L; // 64 位的 long 型普通变量public synchronized void set(long l) { //对单个普通变量的写用同一个锁同步a = l; }public synchronized long get() { //对单个普通变量的读用同一个锁同步return a; }public void getAndIncreament() { //普通方法调用long temp = get(); //调用已同步的读方法temp += 1L; //普通写操作 set(temp); //调用已同步的写方法}
}
如上面示例程序所示,对一个 volatile 变量的单个读/写操作,与对一个普通变量的读/写操作使用同一个锁来同步,它们之间的执行效果相同。
锁的 happens-before 规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入。
锁的语义决定了临界区代码的执行具有原子性。这意味着即使是 64 位的 long 型和 double 型变量,只要它是 volatile变量,对该变量的读写就将具有原子性。如果是多个 volatile 操作或类似于 volatile++ 这种复合操作,这些操作整体上不具有原子性。
简而言之,volatile 变量自身具有下列特性:
可见性。对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入。
原子性:对任意单个 volatile 变量的读/写具有原子性,但类似于 volatile++ 这种复合操作不具有原子性。
4.2 VOLATILE 写-读的内存定义
当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
假设上面的程序 flag 变量用 volatile 修饰
4.3 VOLATILE 内存语义的实现
下面是 JMM 针对编译器制定的 volatile 重排序规则表:
为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
下面是基于保守策略的 JMM 内存屏障插入策略:
在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
在每个 volatile 读操作的后面插入一个 LoadStore 屏障。
下面是保守策略下,volatile 写操作 插入内存屏障后生成的指令序列示意图:
下面是在保守策略下,volatile 读操作 插入内存屏障后生成的指令序列示意图:
上述 volatile 写操作和 volatile 读操作的内存屏障插入策略非常保守。在实际执行时,只要不改变 volatile 写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。
5.1 锁
5.2 锁释放和获取的内存语义
当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中。
当线程获取锁时,JMM 会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量。
5.3 锁内存语义的实现
借助 ReentrantLock 来讲解,PS: 后面专门讲下这块(ReentrantLock、Synchronized、公平锁、非公平锁、AQS等),可以看看大明哥的博客:http://cmsblogs.com/?p=2210
5.4 CONCURRENT 包的实现
如果我们仔细分析 concurrent 包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为 volatile;
然后,使用 CAS 的原子条件更新来实现线程之间的同步;
同时,配合以 volatile 的读/写和 CAS 所具有的 volatile 读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic 包中的类),这些 concurrent 包中的基础类都是使用这种模式来实现的,而 concurrent 包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent 包的实现示意图如下:
6. final
对于 final 域,编译器和处理器要遵守两个重排序规则:
在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序。
6.1 写 FINAL 域的重排序规则
写 final 域的重排序规则禁止把 final 域的写重排序到构造函数之外。这个规则的实现包含下面2个方面:
JMM 禁止编译器把 final 域的写重排序到构造函数之外。
编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障。这个屏障禁止处理器把 final 域的写重排序到构造函数之外。
6.2 读 FINAL 域的重排序规则
在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM 禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读 final 域操作的前面插入一个 LoadLoad 屏障。
6.3 FINAL 域是引用类型
对于引用类型,写 final 域的重排序规则对编译器和处理器增加了如下约束:
在构造函数内对一个 final 引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
7. 总结
7.1 JMM,处理器内存模型与顺序一致性内存模型之间的关系
JMM 是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型,处理器内存模型和顺序一致性内存模型的强弱对比示意图:
7.2 JMM 的设计示意图
7.3 JMM 的内存可见性保证
Java 程序的内存可见性保证按程序类型可以分为下列三类:
1.单线程程序。单线程程序不会出现内存可见性问题。编译器,runtime 和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
2.正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是 JMM 关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
3.未同步/未正确同步的多线程程序。JMM 为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。
下图展示了这三类程序在 JMM 中与在顺序一致性内存模型中的执行结果的异同:
《深入理解 Java 内存模型》读书笔记(下)(干货,万字长文)相关推荐
- 《深入理解 Java 内存模型》读书笔记(上)(干货,万字长文)
目录 0. 前提 1. 基础 1.1 并发编程的模型分类 1.1.1 通信 1.1.2 同步 1.2 JAVA 内存模型的抽象 2. 重排序 2.1 处理器重排序 2.2 内存屏障指令 2.3 HAP ...
- 深入理解 Java 内存模型(转载)
摘要: 原创出处 http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/ 「zhisheng」欢迎转载,保留摘要,谢谢! 0. 前提 &l ...
- 深入理解 Java 内存模型 JMM
前提 <深入理解 Java 内存模型>程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结.全书页数虽不多,内容讲得挺深的.细看的 ...
- 深入理解 Java内存模型
深入理解 Java内存模型 原文地址:http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/ 本文主要内容有 Java 内存模型的基础.重 ...
- 聊聊高并发(三十三)Java内存模型那些事(一)从一致性(Consistency)的角度理解Java内存模型
可以说并发系统要解决的最核心问题之一就是一致性的问题,关于一致性的研究已经有几十年了,有大量的理论,算法支持.这篇说说一致性这个主题一些经常提到的概念,理清Java内存模型在其中的位置. 一致性问题更 ...
- 全面理解Java内存模型(JMM)及volatile关键字
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...
- 深入理解Java内存模型
深入理解Java内存模型(一)--基础 深入理解Java内存模型(二)--重排序深 入理解Java内存模型(三)--顺序一致性 深入理解Java内存模型(四)--volatile 深入理解Java内存 ...
- java if在内存中_Java内存模型知识点小结---《深入理解Java内存模型》(程晓明)读书总结...
一.Java内存模型介绍 内存模型的作用范围: 在Java中,所有实例域.静态域和数组元素存放在堆内存中,线程之间共享,下文称之为"共享变量".局部变量.方法参数.异常处理器等不会 ...
- 深入理解Java内存模型(四)——volatile
2019独角兽企业重金招聘Python工程师标准>>> volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好 ...
- 并发编程专题——第一章(深入理解java内存模型)
说到并发编程,其实有时候觉得,开发中真遇到这些所谓的并发编程,场景多吗,这应该是很多互联网的在职人员,一直在考虑的事情,也一直很想问,但是又不敢问,想学习的同时,网上这些讲的又是乱七八糟,那么本章开始 ...
最新文章
- Linux_LAMP 最强大的动态网站解决方案
- 智能家居(草纲)v0.1
- 数据结构-顺序栈、链栈
- 全球及中国高速公路行业运营管理模式与经营效益分析报告2022版
- 清空缓存的命令_超详细的mysql数据库查询缓存原理解析、涉及命令、流程分析等...
- 前端笔记-thymeleaf显示数据及隐藏数据
- 如何制作一个360度全景
- hdu 2037(今年暑假不AC)
- ORA-27101 Shared memory realm does not exist 之解決 (转)
- Spring Cloud(5)---基于 Spring Cloud 完整的微服务架构实战
- 52 - 算法 - LeetCode 28 - 实现 strStr() -kmp
- DevExpress.XtraGrid 导出文本的bug
- Promise 最完整介绍与实现解密
- 电脑自带蓝牙与HC-06蓝牙模块使用串口助手通信
- 【mock】数据模板定义规范DTD 数据占位符定义规范DPD
- 计算机电子贺卡制作圣诞节,如何制作电子圣诞贺卡?贺卡制作步骤
- 2021年开始,Adobe Flash Player 不能用了?
- 2021年WordPress博客装修美化(一)
- java大文件pdf水印_java – 如何扩展PDF的页面大小以添加水印?
- 玩《刀塔传奇》,玩的就是一种策略
热门文章
- Heartbeat+ipvsadm+ldirectord组建linux高可用集群
- The organization of a typical MVC application
- 【总结】DIV+CSS有可能遇到的问题
- Nginx之负载均衡
- PCL点云库实现点云表面的法线与曲率计算并可视化
- 协程,又称微线程和纤程
- vm15+ubuntu+hadoop3.2,新手小白血泪经验
- Android中四种启动模式,最容易理解的小白教程
- 10、软件质量工程师指南 - 软件项目角色指南系列文章
- Linux监控(添加自定义监控项,配置邮件告警)