1

章三 JMM

本文是JVM系列第三篇,主要描述java内存模型,包括原子操作、指令重排序、可见性、有序性等相关内容,是java并发编程核心原理与基础

<!-- TOC -->

<!-- TOC -->

JMM介绍

JMM是java提供的抽象模型,描述了在多线程环境中,主内存工作内存的交互,主要目标是屏蔽硬件和操作系统的差异。

  • 主内存:主内存用来存储所有共享变量
  • 工作内存:线程独享内存,类似cpu缓存,线程从主内存读取共享变量到工作内存,或者将修改后的数据回写到主内存
  1. 内存模型特性

    • 原子性:原子性表示一个操作不可分割,线程无法观察到其执行过程的中间态。例如基本变量的赋值(int a=10),通过synchronized或atomic实现的操作
    • 可见性:可见性表示线程多共享变量的修改对其他线程可见,例如使用volatile、synchronized、final关键字修饰
    • 有序性:有序性表示代码的执行顺序符合程序的逻辑顺序,但由于编译器和cpu的优化,实际执行顺序可能与代码顺序不一致(又称为指令重排序
  2. 关键概念

    • 主内存和工作内存的交互

      • lock:作用于主内存,将变量标记为线程独占状态
      • unlock:作用于主内存,解除某个内存地址的锁
      • read:从主内存读取共享变量
      • load:加载到工作内存
      • use:工作内存中使用变量
      • assign:修改变量
      • store:变量回写到主内存
      • write:将存储的值写回到主内存
    补充说明:
       1. lock和unlock通常是原子性操作
       2. read/load和store/write:单次操作通常是原子性,例如32为的普通变量在jvm实现中读取和写入是原子操作。
          64位的long和double类型变量在非volatile修饰是,read/write由于可能会分为两个32位块操作,导致非原子性
       3. use/assign,对变量执行简单的赋值是原子型的,但 i++/i--非原子性
       4. 多个内存组合操作通常不保证原子性
          例如:
          int i=0;
          i++;
          操作等价:
          a) read i:从主内存读取i
          b) load i:将i加载进工作内存
          c) use i: 使用i值
          d) assign i+i: 计算并重新赋值i==1
          e) store i: 将新值存储到主内存
          f) write i: 写会主内存
    
    • happens-before 原则:happens-before是jmm的核心规则,定义两个操作之间的顺序关系,如果A操作 happens-before B操作,那么A操作的结果对B操作可见

      • 程序顺序原则:单线程中,前面的操作happens-before后面的操作
      • 锁规则:一个程序释放锁,happens-before另一个线程获取锁
      • volatile:对一个volatile变量的写操作happens-before之后的操作
      • 线程启动规则:线程A调用线程B的start方法,happens-before线程B的执行
      • 线程终止规则:线程B的终止happens-before线程A通过Thread.join()等方法获知线程B的终止
  3. 指令重排序

    • 原因

      • 编译器优化:提高代码执行效率
      • 处理器优化:cpu使用乱序来提高流水线利用率
    • 影响:指令重排序可能导致程序在多线程环境下执行结果与预期不同

      class Singleton {
        private static Singleton instance;
      
        public static Singleton getInstance() {
             if (instance == null) { // 第一次检查
                  synchronized (Singleton.class) {
                    if (instance == null) { // 第二次检查
                      instance = new Singleton();
                    }
                 }
             }
          return instance;
       }
      }
      instance = new Singleton() 包含以下步骤:
      分配内存
      初始化对象;
      将引用指向对象。
      重排序可能导致步骤 2 和步骤 3 交换,其他线程可能访问未完全初始化的对象。
  4. volatile: java并发编程中的轻量同步机制,仅用来保证可见性和禁止重排序

    • 内存屏障:

      • 写屏障:在写操作前插入,确保写操作前的指令不被重排序到屏障后
      • 度屏障:在读操作后插入,确保读操作后的指令不会重排序到屏障前
    • volatile保证线程从主内存读取最新值,并将修改立刻写回到主内存

      • 普通变量:线程可能从工作内存中读取缓存的旧值
      • volatile:线程总是从主内存读取最新值,避免数据不可见问题
    • 局限:

      • 不保证原子性:虽然保证了可见性,但不能保证复合操作的原子性,例如i++涉及多个内存操作
      • 适用场景有限:只适用于状态标志、简单读写等场景,复杂线程安全问题仍需要synchronized或lock、Atomic

        本文章同步发送到公众号‘编程智汇’,欢迎关注

树荫下的影
3 声望0 粉丝

« 上一篇
JVM调优篇