1.JMM是什么?

jmm是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在本地内存数据不一致,编译器会对代码指令重排序,处理器会对代码乱序执行带来的问题

JMM并不是实际存在的内存模型,它是一个规范,主要围绕并发过程中如何处理可见性,原子性,有序性这三方面而建立的模型

JMM规定了所有变量存储在主内存里面,每个线程有自己的工作内存,线程的工作内存保存了该线程使用到的变量的主内存的拷贝

线程对变量的造作都是在工作内存里面完成的,不能直接读写主线程

不同线程之间不能相互访问对方工作线程里面的变量,线程之间的值传递都要通过主内存来完成

主内存:对应计算机里面的内存,从某种程度上来说的话包含堆和方法区

工作内存:是JMM的一个抽象的概念,实际并不存在,它包含了缓存,写缓冲区,寄存器,程序计数器,虚拟机栈,本地方法栈以及其他硬件以及编译器优化之后的一个数据存放位置

2.JMM解决什么问题?

主要是为了解决原子性,可见性和有序性三个问题

原子性:类似于事务的概念,所有操作要么都执行,要么都不执行,在java里面,大部分基础数据类型都是原子操作,long和double类型在32位操作系统里面不是原子操作,因为long和double是64位数据类型,这也意味着在32位操作系统下long和double多线程环境下是不安全的

在java里面可以通过synchronized关键字或者Lock对代码块加锁保证原子性

可见性:一个线程对一个变量修改后其他线程可以立即看到(感知到)对应的变化

单线程环境下不存在可见性问题,因为单线程环境都是顺序操作的,多线程环境下会出现可见性问题,比如一个线程A把一个变量load到内存里面并对他进行修改,还未写回主存,这是另外一个线程B又来读取这个变量,但此时A线程工作内存里面的共享变量对B线程并不可见,这就是可见性问题

在java里面可以通过synchronized关键字,lock,volatile,final实现可见性

有序性:在jvm里面会对没有数据依赖的代码进行重排序,比如1,2,3可能重排序成3,2,1,这种情况在单线程环境下不会有什么问题,但是多线程环境下就可能会有问题了

在java里面可以用volatile防止指令重排来保证一定的有序性,也可以使用synchronized和Lock保证同一时刻只有一个线程在操作

3.JMM的八大原子操作

lock:锁定,作用于主内存的变量,把一个变量标记为一个线程独占状态
unlock:解锁,作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read:读取,作用于主内存变量,把一个变量值从主内存读出来,以便于随后的load工作使用
load:载入,将主内存读出来的变量值放到工作内存的变量中
use: 作用于工作内存变量,把工作内存的变量值传递给执行引擎
assign:赋值,作用于工作内存变量,把一个从执行引擎接收到的值赋值给工作内存的变量
store:作用于工作内存变量,把工作内存变量值传送到主内存中,以便于随后的write操作使用
write:作用于工作内存变量,把store操作从工作内存中的一个变量值传送到主内存变量中

如果要把一个变量值从主内存复制到工作内存,那么就要按顺序执行read和load
如果要把工作内存里面的变量值赋值到主内存中,那么就要按顺序执行store和write操作
但是java内存模型只是规定上述操作要按照顺序执行,但是不保证是连续执行的
同步规则分析:
不允许一个线程无原因地(没有发生任何 assign 操作)把数据从工作内存同步回主内存中;
一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load 或者 assign)的变量。即就是对一个变量实施 use 和 store 操作之前,必须先自行 assign 和 load 操作;
一个变量在同一时刻只允许一条线程对其进行 lock 操作,但 lock 操作可不被同一线程重复执行多次,多次执行 lock 后,只有执行相同次数 unlock 操作,变量才会被解锁。lock 和 unlock 必须成对出现;
如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用变量之前需要重新执行 load 或 assign 操作初始化变量的值;
如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作;也不允许去 unlock 一个被其他线程锁定的变量;
对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行store 和 write 操作)。

4.happens-before

原则:

如果一个操作happends-before另外一个操作,那么第一个操作执行结果对第二个操作可见,且第一个操作执行顺序排在第二个操作之前

两个操作之间存在happends-before关系,并不意味着要一定按照happends-before原则制定的顺序来执行,如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。

锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行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等待返回后可见。

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;

对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

上面是原生的Happens-before规则,但是我们可以对他们推导出其他满足happens-before的规则:

1.将一个元素放入一个线程安全的容器里happens-before从容器里取出这个元素

2.在CountDownLatch上的倒数操作Happens-before countDownLatch的await操作

3.释放Semaphore许可操作happends-before获得许可操作

4.Future表示的任务的所有的操作happends-before Future的get操作

5.向一个Executor提交一个Runnable或Callable的操作Happens-before任务开始执行操作

如果两个操作或者多个操作不能满足以上任意一个规则,那就代表没有顺序保障,jvm就可以对这操作进行重排序,也就是多线程不安全的

as-if-serial:不管怎么重排序,单线程下的执行结果都不能改变,编译器,runtime和处理器都必须遵守as-if-serial语义

为了遵循as-if-serial语义,数据之间存在依赖关系的不会进行重排序,因为这种重排序可能会影响最终执行结果,但是,如果操作之间不存在数据依赖关系,那这些操作就可能会被重排序了

5.volatile如何保证可见性

volatile保证了数据可见性,一定程度的有序性,无法保证原子性

可见性保证:在汇编层面增加lock使用缓存一致性协议(mesi)解决并发可见问题

首先cpu会根据共享变量是否带有volatile字段,决定是否使用MESI协议保证缓存一致性,如果有volatile关键字,在汇编层面会加上LOCK前缀,当一个变量被修改后会马上经过store,write等原子操作修改主内存值(如果不加lock前缀不会马上同步),为了触发cpu的嗅探机制,及时将其他线程的变量副本置为失效

cpu总线嗅探机制监听到这个变量被修改,就会把其他线程的变量副本由共享S置为无效I,当其他线程在使用变量副本时,发现其已经无效,就回去主内存中拿一个最新的值。

变量被修改后写入主内存时需要加锁,写完之后解锁,这个锁只有在修改时才会加,锁力度非常小,因为在store是可能已经过了总线,但是还没write进主存,总线却触发了嗅探机制,其他线程变量已经失效,当其他线程去主内存读最新数据时,新数据还未write进来,产生脏数据

有序性保证:happends-before里面有一条规则是:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。

Java 编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。

重排规则表:

" NO " 表示禁止重排序。

为了实现 volatile 内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM 采取了保守的策略。

  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。

volatile 写是在前面和后面分别插入内存屏障,而 volatile 读操作是在后面插入两个内存屏障。

内存屏障 说明
StoreStore 屏障 禁止上面的普通写和下面的 volatile 写重排序。
StoreLoad 屏障 防止上面的 volatile 写与下面可能有的 volatile 读/写重排序。
LoadLoad 屏障 禁止下面所有的普通读操作和上面的 volatile 读重排序。
LoadStore 屏障 禁止下面所有的普通写操作和上面的 volatile 读重排序。

原子性保证 :volatile可以保证单次读写的原子性,如long和double,但是并不能保证i++这种操作的原子性,因为本质上i++是复合操作:先读取i的值,对i加1,将i的值写回内存,volatile无法保证这三个操作的原子性(如果需要的话我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性)

JMM与volatile相关推荐

  1. Java面试题(二)JMM,volatile,CAS

    目录 1. JMM 2. volatile 2.1. volatile原理 3. CAS 3.1. CAS是什么? 3.2. CAS原理 3.3. CAS底层 3.4. CAS的缺点 3.5. ABA ...

  2. 全面理解Java内存模型(JMM)及volatile关键字

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...

  3. 一文读懂Java内存模型(JMM)及volatile关键字

    点赞再看,养成习惯,公众号搜一搜[一角钱技术]关注更多原创技术文章. 本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章. 前言 并发编程从操作系统底层工作的整 ...

  4. 深入理解Java内存模型(JMM和volatile关键词)

    目录 •写在前面 •物理机三级缓存 •乱序执行优化 •Java内存模型 •内存间交互操作 •volatile型变量 •先行发生原则 •写在前面 在正式讲解之前呢,我们先来讨论讨论硬件的效率与一致性.这 ...

  5. JMM、Volatile及单例模式解析

    文章目录 前言 一.并发编程三要素 1.原子性 2.可见性 3.有序性 二.锁的互斥和可见性 三.CPU缓存架构 四.Java内存模型(JMM)以及共享变量的可见性 1. JMM内存模型 2. JMM ...

  6. Java并发编程:JMM和volatile关键字

    Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区.高速缓存区的加入使得CP ...

  7. Java 理论与实践: 修复 Java 内存模型,第 2 部分 (VOLATILE, FINA...

    为什么80%的码农都做不了架构师?>>>    活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型(Java Memory Model, JMM)的公开建议 ...

  8. JMM内存模型如何为并发保驾护航

    一.为何引入JMM 每个处理器在执行任务时,不可能单靠"计算"就可以完成所有任务,处理器至少需要和内存交互,进行读取运算数据.存储运算结果等,这个I/O操作是很难消除掉的.但由于计 ...

  9. 深入理解volatile

    在理解volatile之前,我们先来看下CPU的工作模式: 处理器这种工作产生的问题: 1.所有的变量在处理器运算期间都是变量对应值的一个副本,其它处理器无法感知其对变量的操作. 2.处理器为了高效利 ...

最新文章

  1. 第五周项目三-时间类(1)
  2. 典型数据中心能耗分析,空调系统选择很重要,想节能可以这样设计
  3. Hyper-V下的Linux虚拟机网卡丢失问题原因及解决办法
  4. 音视频技术开发周刊(第128期)
  5. 273. 整数转换英文表示(模拟)
  6. Java BigDecimal add()方法与示例
  7. JeecgBoot 2.4 微服务正式版发布,基于SpringBoot的低代码平台
  8. Rsyslog+LogAnalyzer+MySQL部署日志服务器
  9. python-rrdtool python-pyrrd
  10. 电机 matlab 仿真 实验总结,哈工大 电机学 MATLAB 仿真 实验报告.docx
  11. 硬件学习笔记之稳压二极管
  12. ART模式下dex2oat出错导致系统无法正常启动
  13. Template.js
  14. 计算机打文档的技巧,电脑word文档下划线怎么打(word文档编辑小技巧)
  15. SHELLEXECUTEINFO控制外部进程
  16. FPGA图像处理_中值滤波、均值滤波、极值滤波
  17. 第一次独立使用大型无人船记录日志—第2天
  18. 如何利用AI技术在零售业做产品创新——京东无人超市的成长之路
  19. centos系统中php Curl 无法访问https 的解决办法
  20. 【转】PC机安装MAC虚拟机

热门文章

  1. 关于Mantel Test
  2. 程序员求职面试心经40条——谨记原则
  3. python安装xlrd模块_python里面的xlrd模块
  4. jdb java_java jdb命令详解
  5. 基于STM32F103ZET6主控平台实现(SPI接口)OLED液晶屏驱动
  6. 【听】情商,理性和感性之间的平衡
  7. Hibernate Inverse属性
  8. 连续三年缩招,北交电信竞争愈演愈烈
  9. 论文解析:Capsule Network
  10. java使用Graphics在图片上绘制形状