JMM知识点总结

一、什么是JMM?

不知道大家在学习的过程有没有思考过这两个问题

  1. 为什么说java是跨平台语言
  2. 导致并发问题的原因是什么

第一个问题,我是这么理解的,代码运行本质上是将我们写的语言转换为操作系统可运行的指令集,而不同的操作系统可能有不同的cpu,对应了不同的指令集,比如windows就是不开源的,它只能运行在intel 86架构的cpu上,不能运行在ARM上,但linux开源,所有它有不同的版本,运行在不同的架构上,java针对这种情况,自己设计了一套内存模型来屏蔽这种差异,这也是JMM存在的原因之一。

第二个问题,才是JMM发光发热的原因。现代计算机越来越复杂,但也越来越快,处理的东西也越来越多,其原因之一是现代计算机采用了多核结构,也就是存在多个CPU core,cpu多是可以同时处理不同的事情,但硬件发展还根本上这么大的进步,cpu处理数据与内存处理数据不匹配,因此,又弄出了CPU缓存模型,也就是cpu cache,现代计算机将CPU cache又细分为三层,对应了L1,L2,L3,但是这又带来了新问题,不同的cpu core通常有自己的cache,在数据读写的过程中,难免出现数据不一致现象,cpu解决这个问题是制定了MESI协议等缓存一致协议,规定CPU在高速缓存和主内存交互时遵循的规范,而操作系统也要解决这个问题,原因是操作系统虚拟化了底层硬件,不同的cache在它眼里就是一样的,OS的解决办法就是指定内存模型来进行规范,无论是windows还是linux都有自己的内存模型。此外,发生并发安全问题的还有另一个因素,就是指令集重排序,它是编译器和处理器对代码的一种优化,但是又没有那么智能,有时候也会帮了倒忙。

JAVA虽然可以使用操作系统自己的内存模型,但作为跨平台语言,不能依靠操作系统,因此,它制定了自己的内存模型,规范了java源代码转换为CPU指令的过程,同时java模型也进行了优化,原本是java线程操作同一个主存,优化后抽象出一层“本地内存”(硬件上就是寄存器),这样就有了类似CPU缓存一致性问题,所有又赋予JAVA内存模型其他特性(原子性,可见性,有序性)和原则(happens-before原则)

二、JMM的三大特性

  1. 原子性
  • 原子性存在多个计算机领域中,它的意思就是一组操作要么全部执行,要么全部不执行,众所周知,i++并不是一组原子操作,java中原子操作有基本类型的赋值(有些情况下,double和long类型并不是原子操作),所有reference引用的赋值操作,Atomic中的操作

  • 原子性如何保证:

    java中有两种操作保证原子性,一种是加锁如synchronized,另一种就是CAS算法思想设计的类

  1. 有序性
  • CPU和编译器会对代码进行一些优化,就是指令重排序优化,表现的现象为指令和java代码的顺序可能不一致,这就导致在并发编程的时候会出现一些稀奇古怪的问题

  • 代码

    public class OutOfOrderExecution {
    
        private static int x=0,y=0;
    private static int a=0,b=0; // 禁止重排序
    // private volatile static int x=0,y=0;
    // private volatile static int a=0,b=0; public static void main(String[] args) throws Exception {
    int i = 0;
    for (; ; ) {
    i++;
    a=0;
    b=0;
    x=0;
    y=0;
    CountDownLatch latch = new CountDownLatch(1);
    Thread one = new Thread(new Runnable() {
    @Override
    public void run() {
    a = 1;
    x = b;
    }
    }); Thread two = new Thread(new Runnable() {
    @Override
    public void run() {
    b = 1;
    y = a;
    }
    });
    one.start();
    two.start();
    one.join();
    two.join();
    String result="第"+i+"次"+"x= " + x + ",y= " + y;
    if(x==0&&y==0){
    System.out.println(result);
    break;
    }else{
    System.out.println(result);
    } }
    }
    }
  • 代码分析

    正常存在三种情况

    • a=1,x=b,b=1,y=a --------> x=0,y=1
    • b=1,y=a ,a=1,x=b --------> x=1,y=0
    • a=1,b=1,y=a,x=b --------> x=1,y=1 (发生线程上下文切换)

    异常情况

    • y=a,a=1,x=b,b=1(不一定这么排) --------> x=0,y=0 (发生了指令重排序)

    如果在本线程中,任何操作都是有序的,这里的有序可以理解为串行,但是从另一个线程观察这个线程,这个线程中所有操作都是无序的,因为存在指令重排序优化

  • 有序性如何保证

    volatile,synchronized都可以保证并发的有序性,volatile不解释,自身都有语义禁止重排序,synchronized保证的同一时刻一个变量(或者一段代码)只能由一个线程操作,这保证了as-if-serial语义,as-if-serial语义简单理解就是单线程环境下,重排序不能影响结果(如果单线程下,重排序导致结果错了,cpu和编译器纯纯坏蛋!)

  1. 可见性
  • 可见性问题的根本原因就是CPU缓存存在多个,如果只有一个就不会发生可见性问题,所谓可见性问题就是别的线程改了某值,但是其他线程不一定知道,然后芭比q了

  • 代码

    public class FieldVisibility {
    int a=1;
    int b=2;
    // volatile int a=1;
    // volatile int b=2; public static void main(String[] args) throws Exception{ while(true){
    FieldVisibility test = new FieldVisibility();
    new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    test.change();
    }
    }).start(); new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    test.print();
    }
    }).start();
    } } private void print() {
    System.out.println("b= "+b+",a="+a);
    } private void change() {
    a=3;
    b=a;
    }
    }
  • 结果分析

    正常情况

    b=2,a=1 先运行print线程,再运行change线程

    b=3,a=3 先运行change线程,再运行print线程

    b=2,a=3 先运行print线程,然后发生上下文切换运行change线程,再运行print线程

    异常情况

    b=3,a=1 change运行完了,但是print线程没看到a的变换

  • 如何保证可见性

    依旧是volatile,这玩意还有可见性语义,此外还有JMM的灵魂happens-before也是来保证可见性,所谓的happens-before,简单来说这个原则规定动作A发生在动作B之前,那么动作B一定可以看见动作A,当然有着happens-before的synchronized也保证可见性,还有大家容易遗忘的final也能保证可见性,因为final的语义就是只要对象正确构造出来,那么任意线程都可以看见final初始化后的值且不变

4.happens-before原则

有几个规则

  1. 单线程规则:单线程中先运行的对后运行的是可见的,注意这不与重排序矛盾
  2. synchronized规则:先上锁的线程的操作结果对后上锁的线程是可见的
  3. volatile规则
  4. 线程启动规则:新启动的线程一定可以看见启动前操作的结果
  5. 线程join规则:想想main函数等待其他函数是为啥,不就是图他们的结果嘛
  6. happens-before是可以传递的
  7. 工具类中也有happens-before

JMM知识点总结的更多相关文章

  1. 剑指Offer——知识点储备-Java基础

    剑指Offer--知识点储备-Java基础 网址来源: http://www.nowcoder.com/discuss/5949?type=0&order=0&pos=4&pa ...

  2. Java 面试知识点解析(三)——JVM篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  3. 【Java】几道让你拿offer的知识点

    前言 只有光头才能变强 之前在刷博客的时候,发现一些写得比较好的博客都会默默收藏起来.最近在查阅补漏,有的知识点比较重要的,但是在之前的博客中还没有写到,于是趁着闲整理一下. 文本的知识点: Inte ...

  4. 95%的技术面试必考的JVM知识点都在这,另附加分思路!

    概述:知识点汇总 jvm的知识点汇总共6个大方向:内存模型.类加载机制.GC垃圾回收是比较重点的内容.性能调优部分偏重实际应用,重点突出实践能力.编译器优化和执行模式部分偏重理论基础,主要掌握知识点. ...

  5. 整理volatile相关知识点

    前言:volatile关键字在面试中经常被问到,从volatile关键字可以引申出许多知识点,因此有必要对此进行总结.本文根据<深入理解Java虚拟机——JVM高级特性与最佳实践>中的相关 ...

  6. 来,了解一下Java内存模型(JMM)

    网上有很多关于Java内存模型的文章,在<深入理解Java虚拟机>和<Java并发编程的艺术>等书中也都有关于这个知识点的介绍.但是,很多人读完之后还是搞不清楚,甚至有的人说自 ...

  7. 个人知识点总结——Java并发

    Java并发实在是一个非常深的问题,这里仅仅简单记录一下Java并发的知识点.水太深.假设不花大量的时间感觉全然hold不住,可是眼下的精力全然不够,兴趣也不在这 什么是线程安全性 某个类的行为和其规 ...

  8. Java研发工程师知识点总结

    Java研发工程师知识点总结 最近一次更新2017年12月08日 大纲 一.Java基础(语言.集合框架.OOP.设计模式等) 二.Java高级(JavaEE.框架.服务器.工具等) 三.多线程和并发 ...

  9. JMM浅析

    背景 学习Java并发编程,JMM是绕不过的槛.在Java规范里面指出了JMM是一个比较开拓性的尝试,是一种试图定义一个一致的.跨平台的内存模型.JMM的最初目的,就是为了能够支多线程程序设计的,每个 ...

  10. Java并发之内存模型(JMM)浅析

    背景 学习Java并发编程,JMM是绕不过的槛.在Java规范里面指出了JMM是一个比较开拓性的尝试,是一种试图定义一个一致的.跨平台的内存模型.JMM的最初目的,就是为了能够支多线程程序设计的,每个 ...

随机推荐

  1. 【python】绘图坐标轴标题中包含上标或下标

    plt.ylabel("流量($\mathregular{m^3}$/s)")          #绘图坐标轴添加上标$\mathregular{m^3}$,若是下标m_3 不加m ...

  2. Vue RSA加密

    1. 安装jsencrypt npm install jsencrypt 2. 引入jsencrypt // 全局引入 import JSEncrypt from "jsencrypt&qu ...

  3. MyBatis Generator使用方法

    第一步:在resources文件夹下创建一个目录mybatis-generator,在目录mybatis-generator下创建文件generatorConfig.xml(此处的目录名可任意取) 第 ...

  4. Jmeter、Postman之RSA加密登录接口测试

    方法1:直接用在线加密工具进行加密,得到密码 参考地址 https://www.toolscat.com/decode/rsa 输入公钥和密码,直接加密即可   方法2:postman工具 步骤1:接 ...

  5. 学习JavaScript第一周

    三种输出方式,console.log.element.write.alert(): 简单数据类型:数值型.字符串型.布尔类型.undefined.null 复杂数据类型:对象 数据类型的转换:字符串转 ...

  6. midway 框架学习

    最近 和别人一块运维 开源 产品,后台需要用到 midway框架,所以进行学习. 首先就是midway的搭建, 首先 npm init midway ,初始化项目,选择 koa-v3 template ...

  7. 3、HTTP请求头与响应头

    HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送 ...

  8. jsp第4个作业(1)

    <%@ page language="java" import="java.util.*" pageEncoding="utf-8"% ...

  9. Maven-高级

    Mvaen 高级 1 导入其他工程 资源文件 先install到仓库里 然后根据组织名项目名版本号写在dependence里正常导入 <dependence> <groupId> ...

  10. IT工具知识-12:RTL8832AU网卡在WIN10更新KB5015807后出现无法正常连接的一种解决方法

    系统配置 硬件配置 使用网卡为Fenvi的FU-AX1800 USB外置网卡(官网驱动同AX1800P) 问题描述 在win10自动更新了KB5015807出现了wifi开机无法自动连接,wifi图标 ...