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. C#访问MySQL(一):连接查询删除(查删)

    前言: 通过C#连接访问MySQL:连接查询. 1.项目添加MySQL引用: 2.获取数据库一个满足条件的值: public static object GetSingle2(string SQLSt ...

  2. 1903021126 申文骏 Java 第二周作业 代码编写及运行

    项目 内容 课程班级博客链接 19级信计班(本) 作业要求链接 Java第二周作业 博客名称 1903021126 申文骏 Java 第二周作业 代码编写及运行 要求 每道题要有题目,代码(使用插入代 ...

  3. 分布式中间件MyCat 使用

    MySQL 分布式软件MyCAT介绍 目录 MySQL 分布式软件MyCAT介绍 一.MySQL 分布式软件MyCAT介绍 1.1.1 MySQL 分布式软件MyCAT介绍 1.1.2 MyCat 架 ...

  4. Vue实现组件化的基本思路

    Vue.js(以下简称Vue)是时下流行的前端开发库,一般搭配其插件Vue-Router,Vuex一起使用,行业中称为Vue全家桶. Vue使用了MVVM的理念,将表现层(DOM)和数据层进行了分离, ...

  5. fiddler 实现跨域

    static function OnBeforeResponse(oSession: Session) { ... if(oSession.uriContains("要处理的url" ...

  6. jmeter性能测试学习1_配置oracl jdbc连接

    1.导入orcle驱动的jar包 2.添加配置元件选择 JDBC连接配置 3.添加取样器 JDBCrequest 4.添加观察树,运行 配好密码 OK

  7. 《JavaScript高级程序设计》Chapter03 JavaScript语言基础

    目录 Syntax Variable var let const Data Type Undefined Null Boolean Number String Symbol Object Operat ...

  8. 电脑日常维护技巧(windows系统)

    一.磁盘检测 cmd-->chkdsk 二.磁盘修复 cmd-->sfc/scannow 三.删除缓存文件 运行-->%temp%

  9. SSH、SFTP、FTP、Telnet、SCP、TFTP协议的原理

    一.SSH协议1.什么是SSH?SSH全称 安全外壳协议(Secure Shell),,是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境. 如果一个用户从本地计算机,使用SSH ...

  10. ubuntu16.04+win10双系统安装

    先下载Ubuntu到电脑:http://mirrors.ustc.edu.cn/ubuntu-releases/16.04.6/ubuntu-16.04.6-desktop-amd64.iso 准备个 ...