【Java并发】1. Java线程内存模型JMM及volatile相关知识
- Java招聘知识合集:https://www.cnblogs.com/spzmmd/tag/Java招聘知识合集/
该系列用于汇集Java招聘需要的知识点
JMM
并发编程的三大特性:可见性(volatile)、有序性(volatile)、原子性(synchronized)
- JMM跟CPU缓存模型相似,是基于CPU缓存模型来建立的,是标准化的,屏蔽了不同计算机的区别
- JMM隶属于JVM,定义了线程与主内存间的抽象关系,线程间的共享变量存放于主内存
- 每个线程均有私有工作内存(JMM抽象概念,实际不存在),工作内存包含了该线程读写共享变量的副本。如果需要使得变量其他线程可访问,需要加volatile修饰
- 线程实际操作的数据均为其工作内存的变量副本,所以会出现多线程里相同变量无法同步
JMM八大原子操作及volatile的可见性原理
JMM八大原子操作是:lock(锁定)、unlock(解锁)、read(读取)、load(载入)、use(使用)、assign(赋值)、store(存储)、write(写入)。
有如下案例,当flag有volatile修饰时,执行main函数,将输出"flag值已改";无volatile修饰时则A线程一直死循环
public class Demo {
// 没有volatile修饰时,B线程对flag的改动,A线程是不可见的
// 有volatile修饰时,B线程对flag的改动对A线程同样可见
private static boolean flag = false;
public static void setFlag(){
flag = true;
}
public static void main(String[] args){
// A 线程,死循环检查flag的值,如果发生改变,则退出死循环
new Thread(() -> {
while(!flag);
System.out.println("flag值已改");
}).start();
// B 线程,改变flag的值
new Thread(() -> {
setFlag();
}).start();
}
}
在不加volatile修饰时,线程A的JMM操作流程
- 主内存里有共享变量flag=false
- read操作:将变量从主内存里读取到线程的工作内存中
- load操作:将read读取的值从工作内存放到工作内存里的副本变量中
- use操作:将副本变量传递给cpu使用(例子里A线程需要对falg进行取反操作,即使用use从副本变量里取得flag到cpu,再进行取反计算)
- 由于A线程在死循环里,所以每次循环检查flag的值时,均会直接从副本变量里取得flag值
在不加volatile修饰时,线程B的JMM操作流程
- 主内存里有共享变量flag=false
- read操作:将变量从主内存里读取到线程的工作内存中
- load操作:将read读取的值从工作内存放到工作内存里的副本变量中
- use操作:将副本变量传递给cpu使用
- assign操作:B线程将flag设置为了true,此时触发assign操作,将cpu计算所得值赋值给工作内存里的变量副本中
- store操作:将工作内存里的共享变量传入主内存
- write操作:将store传送的值写道主内存变量里,至此主内存内flag=true
- 所以B线程对共享变量的修改,无法同步到A线程
- volatile实现共享变量可见的原理
了解JVM的都知道volatile具备两个特殊规则:
- read、load、use动作必须连续出现
这三个操作将数据从主内存读取到cpu - assign、store、write动作必须连续出现
这三个操作将数据从cpu写回到主内存,也即是赋值语句会马上更新到主内存中去
- read、load、use动作必须连续出现
对变量加了volatile指令后,编译成的汇编指令里会给赋值语句加入lock前缀指令。作用如下:
- 被volatile修饰的变量,在某线程里其数据在工作内存中但凡出现变动,将被立即写回主内存(相当于JMM操作assign、store、write必须连续出现)
- 开启缓存一致性协议,数据回写主内存的操作会引起其他线程里对应缓存数据立即失效(工作内存里的对应变量失效)。此时其他线程需要重新从主内存读取变量,这样就确保了其他线程读取到了最新的数据
- 提供内存屏障功能,使lock指令前后的指令不能重新排序(volatile有序性的原理)
volatile的有序性原理
指令重排序
在不影响单线程执行结果(多线程不保证)的情况下,计算机为了优化性能,会对机器指令进行重新排序优化。重排序遵循as-if-serial和happens-before原则
- as-if-serial:重排序不影响单线程执行结果
- happens-before:定义了一些规则来遵循
对象半初始化问题
对于双重检查锁单例模式,如下代码,在执行lazy = new Singleton();语句时,是有可能被指令重排序的。假设A线程由于重排序,在未初始化完成的情况下,先给lazy赋值了,恰巧赋值后B线程也执行getInstance方法,获取到了不为null的lazy变量,而这时候A线程却并没有初始化完毕单例对象,则B线程将使用半初始化的单例对象,造成错误。这就是经典的对象半初始化问题
对于此问题,只需要在单例变量lazy声明时用volatile修饰即可解决,因为volatile禁止指令重排序public class Singleton {
private volatile static Singleton lazy = null;
private Singleton(){}
public static Singleton getInstance(){
if(lazy == null){
synchronized (Singleton.class){
if(lazy == null){
//1.分配内存给这个对象
//2.初始化对象
//3.设置 lazy 指向刚分配的内存地址
lazy = new Singleton();
}
}
}
return lazy;
}
}
volatile关键字通过“内存屏障”来防止指令被重排序,内存屏障底层依旧是通过汇编的lock来实现的
JMM内存屏障规范
- LoadLoad:[Load1;LoadLoad;Load2] 保证load1的读操作在load2及后续读操作前执行
- StoreStore:保证Store1写操作已刷新至主内存,才进行后续的Store操作
- LoadStore:保证Load1读取结束后,后续的Store才进行
- StoreLoad:保证Store1写操作已刷新到主内存后,才进行后续的Load操作
JVM要求volatile需要执行的内存屏障规范
- 在每个volatile写操作的前面插入一个 StoreStore 屏障。
- 在每个volatile写操作的后面插入一个 StoreLoad 屏障。
- 在每个volatile读操作的后面插入一个 LoadLoad 屏障。
- 在每个volatile读操作的后面插入一个 LoadStore 屏障。
交流&联系
QQ群
欢迎加入Java交流群(qq群号: 776241689 )欢迎关注公众号"后端技术学习分享"获取更多技术文章!
PS:小到Java后端技术、计算机基础知识,大到微服务、Service Mesh、大数据等,都是本人研究的方向。我将定期在公众号中分享技术干货,希望以我一己之力,抛砖引玉,帮助朋友们提升技术能力,共同进步!
博客
原创不易,转载请在开头著名文章来源和作者。如果我的文章对您有帮助,请点赞/收藏/关注鼓励支持一下吧
原创不易,转载请在开头著名文章来源和作者。如果我的文章对您有帮助,请点赞/收藏/关注鼓励支持一下吧
【Java并发】1. Java线程内存模型JMM及volatile相关知识的更多相关文章
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 深入理解Java内存模型JMM与volatile关键字
深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...
- 全面理解Java内存模型(JMM)及volatile关键字
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...
- Java并发编程基础-线程安全问题及JMM(volatile)
什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...
- 深入理解 Java 内存模型 JMM 与 volatile
Java 内存模型(Java Memory Model,简称 JMM)是一种抽象的概念,并不真实存在,它描述的是一组规范或者规则,通过这种规范定义了程序中各个变量(包括实例字段.静态字段和构成数组对象 ...
- java核心技术-多线程之线程内存模型
对于每一种编程语言,理解它的内存模型是理所当然的重要.下面我们从jvm的内存模型来体会下java(不限java语言,严格来讲是JVM内存模型,所有JVM体系的变成语言均适用)的内存模型. 堆: 就是我 ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- Java并发编程:JMM (Java内存模型) 以及与volatile关键字详解
目录 计算机系统的一致性 Java内存模型 内存模型的3个重要特征 原子性 可见性 有序性 指令重排序 volatile关键字 保证可见性和防止指令重排 不能保证原子性 计算机系统的一致性 在现代计算 ...
随机推荐
- Dart 编写Api弃用警告
例如body2在以后的版本将被bodyText1代替 @Deprecated( 'This is the term used in the 2014 version of material desig ...
- DeFi下半场,除了YFI,还有BGV!
自今年夏季开始,DeFi市场便已经进入火热态势,且持续至今.其中,去中心化交易所(DEX)以及各种金融衍生品的出现,吸引了大批资金的进入,资本市场的目光已从传统金融移到了DeFi市场,期望着能够从De ...
- webpack4.X源码解析之懒加载
本文针对Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一.准备工作 首先,init之后创建一个简单的webpack基本的配置,在src目录下创建两个js文件(一个主入口文件和一个非主 ...
- 移动端时间回显iphone出现的问题
new Date(item.startTime.replace(/-/g, '/') dateFormat('hh:mm', new Date(item.startTime.replace(/-/g, ...
- 那些容易犯错的c++保留字
本文首发 | 公众号:lunvey 目前正在学习vc++6.0开发,而这里面使用的是c++98标准. 保留字,也称关键字,是指在变量.函数.类中不得重新声明的名称. c++98中大致有48个保留字,这 ...
- yum安装MySQL8 - Centos8
官方地址:https://dev.mysql.com/doc/refman/8.0/en/linux-installation-yum-repo.html 参考博客地址:https://www.jia ...
- 学习一下 SpringCloud (五)-- 配置中心 Config、消息总线 Bus、链路追踪 Sleuth、配置中心 Nacos
(1) 相关博文地址: 学习一下 SpringCloud (一)-- 从单体架构到微服务架构.代码拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105 ...
- Go语言学习:01-基本语法
目录 基本语法 源文件构成 数据类型 基本类型变量 数组 切片 创建切片 调整容量 字符串与切片 常量 String Map 控制 条件语句 if switch 循环语句 函数 函数定义 函数变量 闭 ...
- 关于KMP算法中,获取next数组算法的理解
参考:KMP入门级别算法详解--终于解决了(next数组详解) https://blog.csdn.net/lee18254290736/article/details/77278769 在这里讨论的 ...
- 04----python入门----文件处理
一.大致介绍 我们在计算机上进行的操作,归根结底是对文件的操作,其实质是由操作系统发送请求,将用户或者应用程序对文件读写操作转换成具体的硬盘指令. 众所周知,内存中的数据是无法永久保存的.在计算机硬件 ...