这里写自定义目录标题

  • Java内存模型(JMM)详解-可见性
  • 什么是JMM
  • JMM存在的意义
  • 为什么示例demo中不会打印 i 的值
  • 如何解决可见性问题
  • **深入理解JMM内存模型**
  • JAVA内存模型总结

Java内存模型(JMM)详解-可见性

  1. 什么是JMM
  2. JMM的意义
  3. 如何解决可见性问题
  4. 深入理解JMM内存模型
  5. JAVA内存模型总结

什么是JMM

JMM即是java内存模型(java memory model),屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。通俗的说,就是对java语言中对多线程执行时的规则,是解决多线程场景下并发问题的一个重要规范。

可以看看一下如下文章:

https://www.cnblogs.com/lfs2640666960/p/11019798.html

JMM存在的意义

  1. 谈到JMM内存模型存在的意义,首先我们先了解到java多线程中存在的问题,有如下几点:
    - 所见非所得
    - 无法肉眼检测程序的准确性
    - 不同的平台上会有不同的表现
    - 错误难重现
  • 具体示例代码demo实现如下
/*** @Author 作者 :@潇兮* @Date 创建时间:2019/9/14 22:46* 类说明:多线程DEMO*/
public class Demo_1 {int i = 0;boolean isRunning = true;public static void main(String[] args) throws InterruptedException {final Demo_1 demo = new Demo_1();new Thread(new Runnable() {public void run() {System.out.println("线程开始执行");while(demo.isRunning){demo.i++;}System.out.println(demo.i);}}).start();Thread.sleep(3000L);demo.isRunning = true;System.out.println("终止");}
}

打印结果为:
可以看到按道理 3s 后打印的结果并没有执行输出 i 的值,这是就说明了多线程问题的第二点肉眼无法检测程序的准确性,将edit-ConfConfiguration改为VM-options:-client,执行程序可以看到可以打印出 i 的值(仅限于jdk32位,jdk64位无论是-server(32位不打印)还是-client都不会打印出i的值),这里就不做演示。可以从这一步得出结论不同的平台上会有不同的表现

为什么示例demo中不会打印 i 的值

首先我们先理解上述demo在内存中的表现(如下图),其中主线程和子线程的工作区分布在cpu和RAM内存中:java内存分为共享内存和线程中独占的内存,主线程(main方法)将线程sleep(3000L)后把isRunning变为false,然后将isRunning写入高速缓存中,最后将主内存中的isRunning改为false,子线程(new Thread)则从高速缓存中读取isRunning的值,判断对比while条件,isRunning变为false时,打印 i,但是没有打印。说明可能是子线程读取时出错,存在可见性问题。
推断一:高速缓存造成的可见性问题
由于存在高速缓存协议,两个高速缓存之间的值应该会极快的同步,从而做到数据的同步。但是demo中长时间看不见打印 i 的值,所以排除高速缓存的可能(高速缓存可以导致可见性的问题,但是时间短到肉眼不可见,在程序级别也可能存在异常情况,demo中明显不是,故排除)。


推断二:指令重排造成的可见性问题

## 什么是指令重排:指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序,遵循as-if-serial语义。as-if-serial语义保证了在单个线程内,指令重排时,最终的结果不会改变。但是在多线程中并不保证。


由此可见指令重排能够造成可见性问题,而java编译器的指令重排发生在JIT编译中
从图一(JIT编译过程)、图二(demo执行结果)中可知道,虽然由于高速缓存中同步了值过去,但是发生JIT指令重排,会将while循环体编译成类似如下代码,当isRunning为true时,将它作为一个值存储起来,当子线程读取isRunning的值时,就不会读取到false,从而也就不会打印出 i 的值。
图一
图二

如何解决可见性问题

   java内存模型规定:对于volatile修饰的变量 v的写入,与所有其他线程后续对v的读同步可见性问题:一个线程修改的变量,能够及时被其他线程所见

使用**volatile**(提出规则)关键字修饰isRunning(修饰后由JVM实现具体规则),可以打印出 i 的值,打印结果如下:

volatile关键字的功能:

  1. 禁用缓存;
    volative变量的访问控制会加个ACC_VOLATILE(图一),可以看到isRUnning被修饰了,去oracle官网可以看到规定了volatile禁用了缓存ccannot be cached,结合上述JIT编译示例图,因为禁用了缓存,所以对于isRunning的读写都直接从主内存中读写,从而禁止了JIT指令重排,保证了可见性(图二)
    https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html/#jvms-4.5
    图一 图二
  2. volatile变量相关的指令不做重排序;

深入理解JMM内存模型

上述描述了java内存模型是什么,下面让我们来剖析java内存模型里有什么东西,加深理解

  1. 什么是共享变量
    java内存模型中定义了 share variable(共享变量)。java内存区域中有共享内存区域(比如堆内存,方法区)线程独占内存区域,存在共享区域的变量叫做共享内存变量,比如说方法区中的一些静态字段,堆中对象中存储的实例字段等。

  2. 什么是冲突
    如果至少一个访问操作时写操作,那么对于同一个变量的两次访问可能是冲突的。java内存模型就是为了解决这些冲突的。

  3. 什么是java线程间操作
    一个程序执行的操作可被其他线程感知或被其他线程直接影响(如多线程间的读写操作),java内存模型只描述线程间的操作,不描述线程内的操作,线程内操作按照线程内语义执行。

  4. 线程间操作有哪些

    1.read操作(一般读,非volatile读)
    2.write 操作(一般写,非volatile写)
    3.volatile read
    4. volatile write
    5.Lock.(锁monitor)、unlock
    6.线程的第一个和最后一个操作
    7.外部操作(例如多个程序访问同一个DB)

  5. 同步规则的定义

    1.对于volatile变量v的写入,与所有其他线程后续对于v的读同步;(这里说的同步即可见,这里写在读前面吗,重点)
    2. 对于监视器m的解锁与所有后续操作对于m加锁同步;(就是 ① 对于加锁解锁不能做指令重排;② 当有两个线程,其中一个线程加锁,其中做的操作结果解锁后是对于另一个加锁线程是可见的,重点)
    3. 对于每个属性值写入默认值(0,false,null)与每个线程对其进行的操作同步 (了解)
    4. 启动线程的操作与线程中的第一个操作同步(了解)
    5. 线程T2的最后一个操作与线程T1发现线程T2已经结束同步(isAlive,join可以判断线程是否终止,了解)
    6. 如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步,通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted
    
  6. Happens-before先行发生原则
    happens-before关系用于描述两个有冲突的动作之间的顺序,如果一个action happens before另一个action,则第一个操作被第二个操作可见,JVM需要实现如下happens-before规则(引用下图):

  7. final在JVM中的处理

    1.final在该对象的构造函数中设置对象的字段,当线程看到该对象时,始终看到该对象的final字段的正确构造版本;
    伪代码示例:读取到的f.x一定是最新的,f.y可能为默认值0,具体代码示例如下

    2。如果在构造函数中设置的字段发生读取,则会看到该字段分配的值,否则将看到默认值;
    伪代码示例:public finalDemo{x=1;y=x};y会等于1,x用final修饰;

    3.读取该共享对象的final成员变量之前,先要读取共享对象;
    伪代码示例:r=new ReferenceObject();k=r.f;这两个操作不能重排序

    4.通常被static final修饰的字段,不能被修改。然而System.in、System.out、System.err被static final修饰,却可以修改的,这是遗留问题,必须允许通过set方式改变,我们将这些字段称为写保护,以区别于普通final字段,举个例子如下图:

    /*** @Author 作者 :@潇兮* @Date 创建时间:2019/9/14 23:45* 类说明:并不一定能重现,很难重现,仅作为理论知识了解即可*/
    public class Demo2 {final int x;int y;static Demo2 f;public Demo2() {x=1;y=2;}static void writer(){f=new Demo2();}static  void reader(){if (f!=null){int i=f.x;int j=f.y;System.out.println("i="+i+",j="+j);//因为x被final修饰,y没有,所以 执行main方法后x的值一定为1,y的值可能为2也可能是0}}public static  void main(String[] args){//Thread1.writer//Thread2.reader}}
  8. Word Tearing字节处理
    有些处理器(尤其是早期的Alphas处理器)没有提供写单个字节的功能。在这样的处理器上更新byte数组,若值是简单的读取整个内容,更新对应的字节,然后将整个内容再写回内存,将是不合法的。
    这个问题有时候称为“字分裂(word tearing)”,更新单个字节有难度的处理器,就需要寻求其他方式来解决问题。因此,在编程过程中,尽量不要对byte[]中的元素进行重新赋值,更不要在多线程程序中这样做(这是java语言规范提出的意见,我们持保留意见,当代处理器基本不会出现这个问题)。

  9. double和long的特殊处理

由于《Java语言规范》的原因,对于非volatile的double、long的单次写操作是分两次进行的(double和long是64位),每次操作其中的32位,这可能导致第一次写入后,读取的值是脏数据,第二次写完后才可以读到正确数据,但是64位JVM内部实现了使得对double和long的类型变为原子性的操作。(多线程的情况下,单线程不会有问题)

JAVA内存模型总结

java虚拟机可以同时支持多个执行线程,若未正确同步,线程的行为可能会出现混淆和违反的直觉。JAVA内存模型规范了当多个线程修改了共享内存中的值时,应该读取到哪个值的规则。由于这部分规范类似于不同硬件体系结构的内存模型,因此这些语义称为java编程语言的内存模型。

Java内存模型(JMM)详解-可见性volatile相关推荐

  1. Java 内存模型 JMM 详解

    转载自 Java 内存模型 JMM 详解 JMM简介 Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性.是否可以重排序等问题的无关具体 ...

  2. Java内存模型(JMM)详解

    在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...

  3. Java内存模型(JMM)详解!

    文章目录 什么是JMM? 现代计算机内存模型 缓存一致性 JMM内存模型与计算机内存模型的关系 线程间通信 JMM三大问题 原子性 可见性 有序性 volaile关键字详解! 什么是JMM? JMM定 ...

  4. JMM(Java 内存模型)详解

    CPU 缓存模型 为什么要弄一个 CPU 高速缓存(CPU Cauche)呢? 类比我们开发网站后台系统使用的缓存(比如 Redis)是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题. C ...

  5. java基础—java内存模型(JMM)CPU架构、缓存一致性、重排序、JMM的实现、JMM保证可见性、有序性问题的详解

    java基础-JMM(CPU架构.JMM保证可见性.有序性) 文章目录 java基础-JMM(CPU架构.JMM保证可见性.有序性) CPU架构 说一说现在计算机存储器的结构层次 使用CPU缓存,会导 ...

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

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

  7. JVM——Java内存模型(JMM)

    关注微信公众号:CodingTechWork,一起学习进步. 软硬件发展概述 Amdahl定律和摩尔定律 1)Amdahl定律:通过系统中并行化和串行化的比重来描述多处理器系统能获得的运算加速能力. ...

  8. java 线程 原子性_深入理解Java多线程与并发框架——Java内存模型与原子性、可见性、有序性...

    欢迎关注专栏<Java架构筑基>--专注于Java技术的研究与分享!Java架构筑基​zhuanlan.zhihu.comJava架构筑基--专注于Java技术的研究与分享! 后续文章将首 ...

  9. java 内存模型JMM解析

    java 内存模型JMM解析 一.CPU多核并发缓存架构解析    1.以往的内存读取    2.后来的内存读取 二.java内存模型实现原理    1.验证上图模型      1)案列代码      ...

  10. Java虚拟机(JVM)与Java内存模型(JMM)学习笔记

    Java虚拟机[JVM]与Java内存模型[JMM]学习笔记 Java虚拟机(JVM) 三种JVM JVM 位置 JVM的主要组成部分及其作用 类加载器 双亲委派机制 沙箱安全机制 Java本地接口( ...

最新文章

  1. “我想在 CSDN 写小说” 评论亮了 | 每日趣闻
  2. 微信8.0全心全意考虑用户需求为企业开展网络营销带来全新思考
  3. 【临实战】CentOS 批量配置
  4. Unity中使用WebView
  5. 最短路径问题(Floyd算法)
  6. [安全攻防进阶篇] 七.恶意样本检测之编写代码自动提取IAT表、字符串及时间戳溯源
  7. 蚂蚁金服OceanBase性价比是传统数据库的十倍
  8. 中秋逢国庆 | 盛世华诞 阖家团圆
  9. Java基础:List集合和Set接口
  10. Java编程:二分查找算法(非递归)
  11. Shader预处理宏、内置状态变量、多版本编译等
  12. L298电机驱动设计(含原理图)
  13. Boost库异步IO
  14. 评估基于功能磁共振成像的脑图谱
  15. 全志r16android sdk,全志R16的android4.4.2平台點亮gc2145(evb30)版本:V1.1(分色排版)...
  16. 小程序加载不出来图片
  17. python 绘制q-q图代码
  18. 简述Python垃圾回收机制
  19. 大于4G的文件无法拷贝到U盘
  20. 除硬件外计算机系统不可缺少的是,银河系重约多少个太阳质量?

热门文章

  1. HTML5期末大作业:山河旅行社网站设计——山河旅行社网站(5页) HTML+CSS+JavaScript 学生DW网页 出行 旅途 游玩
  2. TopoJSON格式规范说明
  3. ​大佬,像这种国外创意PPT逻辑图,你能做出来吗?
  4. Chrome 75 lazy-loading
  5. Word文档进行XXE攻击
  6. 设计模式六大原则——单一职责原则(SRP)
  7. 华硕服务器不分区重装系统,华硕笔记本重装系统不能进入系统怎么办
  8. 第三章第九题(商业:检验ISBN-10)((Business: check ISBN-10))
  9. 说到VDI与IDV的分分合合,不会再有比这更清晰的解释了!
  10. python 缩放图片_python实现按长宽比缩放图片