java内存模型——重排序
线程安全问题概括来说表现为三个方面:原子性,可见性和有序性。
在多核处理器的环境下:编译器可能改变两个操作的先后顺序;处理器可能不是完全依照程序的目标代码所指定的顺序执行命令;一个处理器执行的多个操作,在其他处理器的角度来看,其顺序可能与目标代码所指定的顺序不一致。这种现象就叫重排序。
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型。
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
内存重排序类型:
| 重排序类型 | 含义 |
|---|---|
| LoadLoad重排序 | 该重排序指一个处理器上先后执行两个读内存操作L1和L2,其他处理器对这两个内存操作的感知顺序可能是L2——>L1,即L1被重排序到L2之后。 |
| StoreStore重排序 | 该重排序指一个处理器上先后执行两个写内存操作W1和W2,其他处理器对这两个内存操作的感知顺序可能是W2——>W1,即W1被重排序到W2之后。 |
| LoadStore重排序 | 该重排序指一个处理器上先后执行读内存操作L1和写内存操作W2,其他处理器对这两个内存操作的感知顺序可能是W2——>L1,即L1被重排序到W2之后。 |
| StoreLoad重排序 | 该重排序指一个处理器上先后执行写内存操作W1和读内存操作L2,其他处理器对这两个内存操作的感知顺序可能是L2——>W1,即W1被重排序到L2之后。 |
内存重排序与具体的处理器微架构有关,基于不同微架构的处理器所允许的内存重排序是不同的,这里不再阐述。
重排序可能会导致多线程程序出现内存可见性问题
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序
对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。
常见的处理器都不允许对存在数据依赖的操作做重排序
数据依赖性: 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3种类型:
1.写后读:a=1;b=a;
2.写后写:a=1;a=2;
3.读后写:a=b;b=1;
为了遵守as-if-serial语义,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。因为这种重排序会改变执行结果。
不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
重排序对多线程的影响

当操作1和操作2重排序时

当操作3和操作4重排序时

重排序在这里破坏了多线程程序的语义!
通过加锁同步可解决该问题

为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序
- 无论是编译器还是处理器,都需要遵循以下重排序规则:
- 临界区内的操作不允许被重排序到临界区之外
- 临界区内的操作允许被重排序
- 临界区外的操作之间可以被重排序
- 锁申请与锁释放操作不能被重排序
- 两个锁申请操作不能被重排序
- 两个锁释放操作不能被重排序
- 临界区外的操作可以被重排序到临界区之内
参考资料:
1.Java并发编程的艺术(方腾飞 魏鹏 程晓明 著)
2.Java多线程编程实战指南(黄文海 著)
java内存模型——重排序的更多相关文章
- java内存模型-重排序
数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之 ...
- Java内存访问重排序笔记
>>关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. > ...
- Java 并发系列之三:java 内存模型(JMM)
1. 并发编程的挑战 2. 并发编程需要解决的两大问题 3. 线程通信机制 4. 内存模型 5. volatile 6. synchronized 7. CAS 8. 锁的内存语义 9. DCL 双重 ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- Java内存模型(三)原子性、内存可见性、重排序、顺序一致性、volatile、锁、final
一.原子性 原子性操作指相应的操作是单一不可分割的操作.例如,对int变量count执行count++d操作就不是原子性操作.因为count++实际上可以分解为3个操作:(1)读取变量co ...
- java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)
今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...
- JVM学习(3)——总结Java内存模型
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...
- 浅析java内存模型--JMM(Java Memory Model)
在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...
- 《深入理解Java内存模型》读书总结
概要 文章是<深入理解Java内容模型>读书笔记,该书总共包括了3部分的知识. 第1部分,基本概念 包括"并发.同步.主内存.本地内存.重排序.内存屏障.happens befo ...
随机推荐
- 用MAILX 发送邮件
使用 25 端口发送 mail 编辑/etc/mail.rc 文件,添加以下信息vi /etc/mail.rc set from=xxx@163.com smtp=smtp.163.comset sm ...
- 【NX二次开发】获取视图当前的剪辑边界UF_VIEW_ask_current_xy_clip()
UF_VIEW_ask_current_xy_clip()这个函数网上还没有详细的说明,我花了一点时间,详细得理解了一下函数返回的4个值的意思,作为一个猜想,希望有人能验证一下. 获取视图当前的剪辑边 ...
- Linux命令大全之查看登陆用户信息
1,w +回车 2.who +回车 3.last+回车 4.lastlog+回车
- Vue指令实现原理
前言 自定义指令是vue中使用频率仅次于组件,其包含bind.inserted.update.componentUpdated.unbind五个生命周期钩子.本文将对vue指令的工作原理进行相应介绍, ...
- rust漫游 - 写时拷贝 Cow<'_, B>
rust漫游 - 写时拷贝 Cow<'_, B> Cow 是一个写时复制功能的智能指针,在数据需要修改或者所有权发生变化时使用,多用于读多写少的场景. pub enum Cow<'a ...
- 看CarbonData如何用四招助力Apache Spark
摘要:CarbonData 在 Apache Spark 和存储系统之间起到中介服务的作用,为 Spark 提供的4个重要功能. 本文分享自华为云社区<Make Apache Spark bet ...
- Mongo3基础操作
由于3.X的文档是在3.X当前最新版本前记录,所以这里列出一些常用的操作,比如建立库,删除库,等一些格式,然后在描述开启远程和创建用户的一些区别,以及讲解2.X和3.X配置文件区别. 1. Mongo ...
- 对volatile的理解--从JMM以及单例模式剖析
请谈谈你对volatile的理解 1.volitale是Java虚拟机提供的一种轻量级的同步机制 三大特性1.1保证可见性 1.2不保证原子性 1.3禁止指令重排 首先保证可见性 1.1 可见性 概念 ...
- Redis 底层数据结构之整数集合
文章参考:<Redis 设计与实现>黄建宏 整数集合 整数集合时集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合数量不多时,就会使用整数集合 typedef struct i ...
- redis-cluster集群安装(windows)
在此先奉上安装包(链接:https://pan.baidu.com/s/1QHYQPkYPuiRWhdj9APbjnw 提取码:jv8x ) 1. 安装ruby 下载 rubyinstaller-2. ...