java内存模型的理解
java内存模型的理解
- 并发问题产生的源头
- 缓存导致的可见性问题
- 线程切换导致的原子性问题
- 编译优化带来的有序性问题
- 小结
- Java内存模型: 解决可见性和有序性问题
- Java内存模型与JVM内存模型的区别
- volatile关键字
- Happens-Before规则
- 小结
- 思考题
- 参考
并发问题产生的源头
缓存导致的可见性问题
可见性定义: 一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。
在单核CPU时代,所有线程都是被同一个CPU调度,因此共享同一块cpu的缓存,不存在缓存一致性问题:
多核CPU时代,每个线程都有可能同时被不同的cpu调度,每个cpu都有各自的缓存,并且读写优先走缓存,这就会导致缓存一致性问题:
高速缓存和主内存之间如何保持数据一致性
线程切换导致的原子性问题
CPU能保证的原子操作是CPU指令级别的,高级语言里一条语句往往需要多条 CPU 指令完成,例如:count += 1,至少需要三条 CPU 指令:
编译优化带来的有序性问题
有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序,从而导致意外的bug。
最常见的例子就是双重锁检查创建单例对象了:
public class Singleton {static Singleton instance;static Singleton getInstance(){if (instance == null) {synchronized(Singleton.class) {if (instance == null)instance = new Singleton();}}return instance;}
}
new创建对象在java中分为三步:
- 分配一块内存 M;
- 在内存 M 上初始化 Singleton 对象;
- 然后 M 的地址赋值给 instance 变量。
但是如果编译器进行了指令重排序优化,变成了下面这样:
- 分配一块内存 M;
- 将 M 的地址赋值给 instance 变量;
- 最后在内存 M 上初始化 Singleton 对象。
在下面场景中,线程B访问未初始化过的instance,可能会触发空指针异常:
小结
java并发编程问题三大根本来源: 可见性,原子性,有序性
- 缓存导致的可见性问题
- 线程切换导致的原子性问题
- 编译优化导致的有序性问题
Java内存模型: 解决可见性和有序性问题
Java内存模型与JVM内存模型的区别
-
Java内存模型定义了一套规范,能使JVM按需禁用cpu缓存和禁止编译优化。这套规范包括对volatile, synchronized, final三个关键字的解析,和7个Happen-Before规则。
-
JVM内存模型是指程序计数器,虚拟机栈,本地方法栈,堆,方法区这5和要素。
volatile关键字
volatile在c语言中最原始的含义就是禁用cpu缓存,volatile修饰符表达的是: 对某个变量的读写,不能使用cpu缓存,必须从内存中读取或者写入。
大家看下面这个例子: 线程A执行writer方法,按照volatile语义,会把变量v=true写入内存,假设线程B执行reader方法,同样按照volatile语义,线程B会从内存中读取变量v, 如果线程B看到v==true时,那么线程B看到的变量x的值是多少呢?
class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) {// 这里x会是多少呢?}}
}
答案:
- jdk 1.5之前,x可能是42,也可能是0 ,因为变量x可能被cpu缓存而导致可见性问题
- jdk 1.5之后, x就是等于42。因为java内存模型在1.5版本对volatile语义进行了增强
怎么增强的呢?
- happens-before规则
Happens-Before规则
Happens-Before规则:前面一个操作的结果对后续操作是可见的;
Happens-Before规则约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守Happens-Before规则。
具体规则如下:
- 程序的顺序性规则:在一个线程中,前面的操作Happens-Before于后续的任意操作。
- volatile变量规则:对一个volatile变量的写操作Happens-Before于对这个volatile变量的读操作。
- 传递性规则:A Happens-Before B,B Happens-Before C,那么A Happens-Before C。
从图中,我们可以看到:
- “x=42” Happens-Before 写变量 “v=true” ,这是规则 1 的内容;
- 写变量“v=true” Happens-Before 读变量 “v=true”,这是规则 2 的内容 。
再根据这个传递性规则,我们得到结果:“x=42” Happens-Before 读变量“v=true”。这意味着什么呢?如果线程 B 读到了“v=true”,那么线程 A 设置的“x=42”对线程 B 是可见的。也就是说,线程 B 能看到 “x == 42” ,有没有一种恍然大悟的感觉?这就是 1.5 版本对 volatile 语义的增强,这个增强意义重大,1.5 版本的并发工具包(java.util.concurrent)就是靠 volatile 语义来搞定可见性的。
- 管程中锁的规则:synchronized是Java对管程的实现,隐式加锁、释放锁,对一个锁的解锁Happens-Before于后续对这个锁的加锁。
- 线程start()规则:主线程A启动子线程B后,start()操作 Happens-Before于子操作中的任意操作。
- 线程join规则:在线程A中调用线程B的join()并返回,线程B中的任意操作 Happens-Before于join()的返回。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
上述很多规则都需要配合传递性规则进行理解。
小结
Java内存模型涉及的几个关键词:锁、volatile字段、final修饰符与对象的安全发布。
- 第一是锁,锁操作是具备happens-before关系的,解锁操作happens-before之后对同一把锁的加锁操作。实际上,在解锁的时候,JVM需要强制刷新缓存,使得当前线程所修改的内存对其他线程可见。
- 第二是volatile字段,volatile字段可以看成是一种不保证原子性的同步但保证可见性的特性,其性能往往是优于锁操作的。但是,频繁地访问 volatile字段也会出现因为不断地强制刷新缓存而影响程序的性能的问题。
- 第三是final修饰符,final修饰的实例字段则是涉及到新建对象的发布问题。当一个对象包含final修饰的实例字段时,其他线程能够看到已经初始化的final实例字段,这是安全的。
Java内存模型底层怎么实现的?
- 主要是通过内存屏障(memory barrier)禁止重排序的,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile字段的读写操作前后各插入一些内存屏障。
在java中,Happens-Before规则本质还是一种可见性,A Happens-Before B,意味着A事件对B事件来说是可见的,无论A事件和B事件是否发生在同一个线程里,例如: 事件A发生在线程1,事件B发生在线程2,Happens-Before规则保证线程2上也能看到A事件的发送。
思考题
还是文中给出的案例,大家思考会不会产生x=42和v=true重排序,导致线程B读取到x=0的结果呢?
class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) {// 这里x会是多少呢?}}}
解答:
- 程序顺序性规则是针对单线程的。如果只考虑单线程,那么编译器可以对范例代码进行指令重排优化。但是对于多线程,volatile 变量规则、传递性这 2 条规则,就附加了新的限制。对于多线程,这 3 条 happens-before 规则要求,线程 B 在读到 v = true 的时候,也能见到 x = 42。如果编译器仍然按照单线程的情况,对这两条语句进行指令重排,把 v = true 放到 x = 42 之前。那么,线程 B 就有可能看不到 x 的值为 42。这显然违背了 happens-before 的规定。编译器为了符合规则,只能不进行指令重排优化了。
- 为了符合 happens-before 规定,对于示例代码,编译器不能进行指令重排的编译优化。但实际上,仅仅不进行指令重排编译优化,并不能保证编译后的代码的执行结果,符合 happnes-before 规定。因为指令重排这一优化措施,并不仅仅是编译器会做。现代 cpu 在执行机器指令的时候,同样会做指令重排的优化。所以,如果 cpu 在执行机器指令时,发生了机器指令的重排序。上述实例代码的结果,仍然有机会不符合 happens-before 规定。
- 为了令到编译后的代码的执行结果,能够符合 happens-before 规定。编译器除了不能做指令重排序编译优化之外,还要在生成的机器代码中。加入特定 cpu 指令,令到 cpu 只会执行完这条特定指令之后,才会执行后续的其它机器指令。而这种特定指令,是通过建立 “内存屏障” 来禁止 cpu 的指令重排序的。那为什么叫 “内存屏障” 呢?可以理解为一种特殊指令,要求 cpu 把缓存数据写回到主内存中。这就像在内存中建立了一道屏障,令到后面的代码不能越过屏障,提前执行。
jmm 是一个规范,它用于指导编译器的行为。但它本身不会限制编译器所使用的具体编译技术。所以,在 jmm 规范中,不会提到 “指令重排” 或者 "内存屏障” 这些具体的实现技术。这是我们在学习规范类知识的时候,需要注意的。
参考
JAVA并发编程实战
相关文章:

java内存模型的理解
java内存模型的理解并发问题产生的源头缓存导致的可见性问题线程切换导致的原子性问题编译优化带来的有序性问题小结Java内存模型: 解决可见性和有序性问题Java内存模型与JVM内存模型的区别volatile关键字Happens-Before规则小结思考题参考并发问题产生的源头 缓存导致的可见性…...

自己写一个简单的IOC
什么是SpringIOC? 答:IOC即控制反转,就是我们不在手动的去new一个对象,而是将创建对象的权力交给Spring去管理,我们想要一个User类型的对象,就只需要定义一个User类型的变量user1,然后让Spring去…...

用Python批量重命名文件
案例 今天,我们来整理文件夹中的文件,给某个文件夹下的所有文件重新命名。要求是给所有文件按照修改时间,按顺序在文件名前面加上编号。比如将文件资料.xlsx重命名为1. 资料.xlsx import osdef Get_modify_time(file):return os.path.getmtime(file) #获取文件修改时间path…...

iis之web服务器搭建、部署(详细)~千锋
目录 Web服务器 部署web服务器 实验一 发布一个静态网站 实验二 一台服务器同时发布多个web站点 网站类型 Web服务器 也叫网页服务或HTTP服务器web服务器使用的协议是HTTPHTTP协议端口号:TCP 80、HTTPS协议端口号:TCP 443Web服务器发布软件&…...
javascript的ajax
学什么Ajax基础JSON跨域XHR对象Ajax进阶Ajax应用Ajax扩展Ajax基础初识 AjaxAjax的基本用法GET请求POST请求JSON初识JSONJSON的3种形式JSON的常用方法跨域初识跨域CORS跨域资源共享JSONPXHR 对象XHR的属性XHR的方法XHR的事件Ajax进阶FormData封装Ajax使用Promise改造封装好的Aja…...

SpringBoot入门 - 开发中还有哪些常用注解
本文主要介绍一些SpringBoot中常用的注解。Spring Boot 常用注解SpringBootApplicationTarget(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Inherited Configuration EnableAutoConfiguration ComponentScan public interface SpringBootApplication {/*…...

网络基础(三)
目录 网络层与数据链路层 编辑 网络层 IP协议 IP的构成 TCP和IP各自解决什么问题? 基本概念 协议头格式 协议头分析 如何分离与封装? 如何分用(向上交付)? 16位首部校验和 32位源IP和目的IP 4位版本 8位服务类型(Type Of…...

Go语言函数高级篇
Go语言函数高级篇1.高阶函数函数作为参数函数作为返回值2.匿名函数3.defer4.内置函数1.高阶函数 高阶函数分为函数作为参数和函数作为返回值两部分。 函数作为参数 函数可以作为参数: package mainimport "fmt"func add(x, y int) int {return x y }…...

ubuntu16.04 python代码自启动和可执行文件自启动
1 python代码自启动 参考 https://blog.csdn.net/qq_38288618/article/details/104096606 准备好python文件 test.py import time c1 while 1:time.sleep(1)cc1print(c)运行 sudo chmod 777 test.py python3 test.py准备run.sh 文件 #!/bin/bash gnome-terminal -x bash -…...

应用层协议 HTTP HTTPS
目录 应用层 再谈 "协议" 序列化和反序列化 关于 json库 request序列化 request反序列化 response序列化 response反序列化 PS:命令宏 HTTP协议 认识URL urlencode和urldecode HTTP协议格式 HTTP请求 HTTP响应 请求方法 编辑 HT…...

图神经网络 pytorch GCN torch_geometric KarateClub 数据集
图神经网络 安装Pyg 首先安装torch_geometric需要安装pytorch然后查看一下自己电脑Pytorch的版本 import torch print(torch.__version__) #1.12.0cu113然后进入官网文档网站 链接: https://pytorch-geometric.readthedocs.io/en/latest/install/installation.html 安装自己…...

【博学谷学习记录】超强总结,用心分享丨人工智能 自然语言处理 文本特征处理小结
目录文本特征处理作用常见的文本特征处理方法添加n-gram特征说明提取n-gram文本长度规范说明实现导包问题记录心得文本特征处理作用 文本特征处理包括为语料添加具有普适性的文本特征, 如:n-gram特征 以及对加入特征之后的文本语料进行必要的处理, 如: 长度规范. 这些特征处…...

2023年中职网络安全竞赛解析——隐藏信息探索
隐藏信息探索 需求环境可私信博主,求个三连! 1.访问服务器的FTP服务,下载图片QR,从图片中获取flag,并将flag提交; 2.访问服务器的FTP服务,下载文件document,从文件中获取flag,并将flag提交; 3.访问服务器的FTP服务,下载图片beach,从图片中获取flag,并将flag提交…...

实用操作--迁移到Spring Boot 3 和 Spring 6 需要关注的JAVA新特性
正如你所了解到的,迁移到Spring Boot 3 或 Spring 6也将迁移到Java 17和Jakarta EE 9。 如果你非常重视可观察性和本机可执行文件,将从Spring Boot 和 Spring 的主要发行版中获益最大。 本文针对迁移到Spring Boot 3 或 Spring 6涉及的变化进行了梳理和总结。 1. java17新特…...

等保检测风险处理方案
文章目录等保检测风险处理方案1. Apache HTTP Server "httpOnly" Cookie信息泄露漏洞(CVE-2012-0053))2. 检测到目标web应用表单存在口令猜测攻击3. X-Content-Type-Options响应头缺失4. X-XSS-Protection响应头缺失5. Content-Security-Policy响应头缺失6. Referrer…...

java 包装类 万字详解(通俗易懂)
前言简介和溯源拆装箱String类和基本类型的相互转化String类和包装类型的相互转化八大包装类的常用方法汇总(含代码演示)一、前言 : 本节内容是我们《API-常用类》专题的最后一节了。本节内容主要讲包装类,内容包括但不限于包装类的诞生&…...

为什么我复制的中文url粘贴出来会是乱码的? 浏览器url编码和解码
为什么我复制的中文url粘贴出来会是乱码的? 浏览器url编码和解码 Start 番茄最近涉及到一些和单点登录相关的业务需求,在实现功能的过程中,难免少不了和 url 打交道。但是在打交道的过程中,遇到一个痛点:明明我复制的…...

移动端适配
是看的b站一个老哥的视频,做的汇总,讲的嘎嘎棒。视频链接:b站链接 视口viewport pc端视口就是可视化的窗口,不包含浏览器工具栏但是移动端,不太一样,布局的视口和可见的视口是不太一样的 移动端的网页…...

【FPGA】Verilog:时序电路应用 | 序列发生器 | 序列检测器
前言:本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例:序列发生器与序列检测器 功能特性: 采用 Xilinx Artix-7 XC7A35T芯片 配置方式:USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器…...

Biomod2 (下):物种分布模型建模
这里写目录标题1.给出一个线性回归模型并求出因子贡献度2.biomod22.1 pseudo-absences:伪不存在点(PA)2.1.1 random2.2.2 disk2.2.3 user.defined method3.使用网格划分区域3.1 计算质心4. 完整案例1.给出一个线性回归模型并求出因子贡献度 ##---------…...

Linux性能学习(2.2):内存_进程线程内存分配机制探究
文章目录1 进程内存分配探究1.1 代码1.2 试验过程2 线程内存分配探究2.1 代码2.2 试验过程3 总结参考资料:1. 嵌入式软件开发杂谈(3):Linux下内存与虚拟内存2. 嵌入式软件开发杂谈(1):Linux下最…...

BPMN2.0规范及流程引擎选型方案
BPMN2.0规范及流程引擎选型方案一、基本概念二、BPMN意义三、主要元素3.1 活动任务子流程调用活动事件子流程事务3.2 网关排他网关包容网关并行网关事件网关3.3 事件开始事件结束事件中间事件3.4 辅助泳道图注释与组数据存储四、图类型4.1 编排图4.2 会话图五、技术选型5.1 前端…...

VMware虚拟机安装Linux教程
前言 本文小新为大家带来 VMware虚拟机安装Linux教程 ,后边将为大家分享Linux系统的相关知识与操作,在此之前的第一步我们需要在我们的电脑上搭建好一个Linux系统的环境,本文的具体内容包括VMware虚拟机软件安装与Linux系统安装~ 不积跬步&a…...

多人协作|RecyclerView列表模块新架构设计
多人协作|RecyclerView列表模块新架构设计多人协作设计图新架构设计与实现设计背景与新需求新架构设计多人协作设计图 根据产品设计,将首页列表即将展示内容区域,以模块划分成多个。令团队开发成员分别承接不同模块进行开发,且互不影响任务开…...

SpringBoot (六) 整合配置文件 @Value、ConfigurationProperties
哈喽,大家好,我是有勇气的牛排(全网同名)🐮🐮🐮 有问题的小伙伴欢迎在文末评论,点赞、收藏是对我最大的支持!!!。 1 使用 Value 注解 /** Auth…...

docker 入门篇
docker为什么会出现? 一款产品:开发---->运维,两套环境!应用环境,应用配置! 常见问题:我的电脑可以运行,版本更新,导致服务不可用。 环境配置十分的麻烦,…...

MapReduce的shuffle过程详解
shuffle流程概括 因为频繁的磁盘I/O操作会严重的降低效率,因此“中间结果”不会立马写入磁盘,而是优先存储到Map节点的“环形内存缓冲区”,在写入的过程中进行分区(partition),也就是对于每个键值对来说&a…...

【软件使用】MarkText下载安装与汉化设置 (markdown快捷键收藏)
一、安装与汉化 对版本没要求的可以直接选择 3、免安装的汉化包 1、下载安装MarkText MaxText win64 https://github.com/marktext/marktext/releases/download/v0.17.1/marktext-setup.exe 使用迅雷可以快速下载 2. 配置中文语言包 中文包下载地址:GitHub - chi…...

LeetCode笔记:Biweekly Contest 99
LeetCode笔记:Biweekly Contest 99 1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 1. 解题思路2. 代码实现 比赛链接:https://leetcode.com/contest/biweekly-contest-99 1. 题目一…...

初探富文本之CRDT协同实例
初探富文本之CRDT协同实例 在前边初探富文本之CRDT协同算法一文中我们探讨了为什么需要协同、分布式的最终一致性理论、偏序集与半格的概念、为什么需要有偏序关系、如何通过数据结构避免冲突、分布式系统如何进行同步调度等等,这些属于完成协同所需要了解的基础知…...