乐趣区

关于java:Java并发JMMJava内存模型

大家好,这里是 淇妙小屋 ,一个分享技术,分享生存的博主
以下是我的主页,各个主页同步更新优质博客,创作不易,还请大家点波关注
掘金主页
知乎主页
Segmentfault 主页
简书主页
后续会公布更多 MySQL,Redis,并发,JVM,分布式等面试热点常识,以及 Java 学习路线,面试重点,职业规划,面经等相干博客
转载请表明出处!

为了保障并发编程的个性不被毁坏,提供了以下几种模型

1. 程序一致性模型

程序一致性模型能够保障并发编程的个性不被毁坏,为多线程程序提供了极强的 内存一致性保障

1.1 特点

  • 一个线程中的所有操作必须依照程序的程序来执行
  • (不论程序是否同步)所有线程都只能看到同一个的操作执行程序,所有对内存的操作都是原子的,并且其余线程立刻可见
  • 只有一个全局内存,某一时刻,只有一个线程能够拜访内存 ,多线程并发时, 串行拜访内存

1.2 例子

1.2.1 同步程序

通过加锁实现线程 A 和线程 B 的同步

1.2.2 未同步程序

尽管未同步,操作的执行整体上无序,然而两个线程都能看到执行程序(因为程序一致性模型保障每个线程的操作对其余线程立刻可见)

2. Java 内存模型(JMM)

2.1 什么是 JMM

  • JMM 是一种形象的概念,实在并不存在
  • JMM 是一种标准,定义了一组规定,目标是解决因为多线程通过共享内存进行通信时,存在的本地内存数据不统一、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题
  • JMM 的要求不像程序一致性模型那般严格,它的方针是:对于正确同步的程序,那么在不扭转程序执行后果的前提下,尽可能地为编译器和处理器的优化关上方便之门
  • JVM 根据 JMM 实现

2.2 JMM 有什么作用

  • 每个处理器的内存模型不同,JMM 屏蔽了不同处理器内存模型的差别,它在不同的处理器平台之上为 Java 程序员出现了一个 统一的内存模型
  • JMM 提供内存可见性保障

    • 单线程程序

      不会呈现内存可见性问题

    • 正确同步的多线程程序

      JMM 通过限度 编译器和处理器的重排序来提供内存可见性保障

      JMM 保障正确同步的多线程程序在任意的处理器平台上的执行具备 程序一致性(程序的执行后果与该程序在程序一致性内存模型中的执行后果雷同)

    • 未同步 / 未正确同步的多线程程序

      JMM 为其提供最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值

      JMM 该程序的执行后果与其在数据一致性模型中的执行后果统一

2.3 JMM 形容的规定

  • 定义线程和主内存之间的形象关系
  • 定义了 8 种内存交互操作及其规定
  • 线程之间如何通信(线程之间的通信由 JMM 管制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见)
  • 对指令重排序的限度

2.3.1 线程和主内存之间的形象关系

  • 线程之间的共享变量存储在主内存中
  • 每个线程都有一个 公有的本地内存(抽象概念),本地内存存储了该线程须要应用的共享变量的正本

2.3.2 八种内存交互操作

  • lock(锁定),作用于 主内存 中的变量,把变量标识为线程独占的状态。
  • read(读取),作用于 主内存 的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的 load 操作应用。
  • load(加载),作用于 工作内存 的变量,把 read 操作主存的变量放入到工作内存的变量正本中。
  • use(应用),作用于 工作内存 的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个须要应用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值),作用于 工作内存 的变量,它把一个从执行引擎中承受到的值赋值给工作内存的变量正本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
  • store(存储),作用于 工作内存 的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的 write 应用。
  • write(写入):作用于 主内存 中的变量,它把 store 操作从工作内存中失去的变量的值放入主内存的变量中。
  • unlock(解锁):作用于 主内存 的变量,它把一个处于锁定状态的变量释放出来,开释后的变量才能够被其余线程锁定。

规定

  • 不容许 read、load、store、write 操作之一独自呈现,也就是 read 操作后必须 load,store 操作后必须 write。
  • 不容许线程抛弃他最近的 assign 操作,即工作内存中的变量数据扭转了之后,必须告知主存。
  • 不容许线程将没有 assign 的数据从工作内存同步到主内存。
  • 一个新的变量必须在主内存中诞生,不容许工作内存间接应用一个未被初始化的变量。就是对变量施行 use、store 操作之前,必须通过 load 和 assign 操作。
  • 一个变量同一时间只能有一个线程对其进行 lock 操作。屡次 lock 之后,必须执行雷同次数 unlock 才能够解锁。
  • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值。在执行引擎应用这个变量前,必须从新 load 或 assign 操作初始化变量的值。
  • 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其余线程锁住的变量。
  • 一个线程对一个变量进行 unlock 操作之前,必须先把此变量同步回主内存。

2.3.3 线程之间如何通信

JMM 要求线程之间的通信必须通过主内存

如果线程 A 与线程 B 之间想要通信,那么须要通过 2 个步骤

  1. 线程 A 在本地内存批改后,刷新到主内存中
  2. 主内存同步到线程 B 的本地内存后,由线程 B 读取

2.3.4 对指令重排序的限度

  • 对于编译器重排序,JMM 的编译器重排序规定会禁止特定类型的编译器重排序
  • 对于处理器重排序,JMM 的处理器重排序规定会要求 Java 编译器在生成指定序列时,插入特定类型的 内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序

内存屏障的类型

屏障类型 指令示例 阐明
LoadLoad Barriers Load1; LoadLoad; Load2 禁止指令重排序,Load1 肯定先于 Load2
StoreStore Barriers Store1; StoreStore; Store2 禁止指令重排序,store1 肯定先于 store2; store1 写的数据会立即同步到主内存中,对所有线程可见
LoadStore Barriers Load1; LoadStore; Store2 禁止指令重排序,Load1 肯定先于 Store2
StoreLoad Barriers Store1; StoreLoad; Load2 禁止指令重排序,Store 肯定先于 Load2;store1 写的数据会立即同步到主内存中,对所有线程可见

StoreLoad Barriers 是一个全能型屏障,具备其余 3 个屏障的成果,然而开销高(因为要把缓冲区中的数据刷新到内存中)

3. happens-before 规定

3.1 为什么要有 happens-before

  • JMM 定义的规定难以了解和编程,happens-before 便于程序员了解和编程,每条 happens-before 规定对应着多条 JMM 定义的规定
  • 程序员心愿基于一个强内存模型进行程序编写,但 JMM 为了尽可能施展处理器性能,实现的是一个弱内存模型(JMM 容许不扭转程序运行后果的重排序),所以 JMM 提供 happens-before 规定,向程序员提供足够的内存可见性保障(尽管有的保障并不一定实在存在,例如:如果一个代码块的锁只会被一个线程获取,那么这个锁能够被打消,但在程序员看来就是加锁的)

3.2 happens-before 的定义

  1. 如果一个操作 happens-before 另一个操作,那么第一个操作的执行后果将对第二个操作 可见,而且第一个操作的执行程序排在第二个操作之前(这只是 JMM 对程序员的保障,让程序员认为,正确同步的多线程程序是按 happens-before 指定的程序来执行的,但实际上可能不是)
  2. 两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要依照 happens-before 关系指定的程序来执行。如果重排序之后的执行后果,与按 happens-before 关系来执行的后果统一,那么 JMM 容许这种重排序

3.3 happens-before 内容

  1. 程序程序规定:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
  2. 监视器锁规定:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
  3. volatile 变量规定:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
  4. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
  5. start()规定:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的 ThreadB.start()操作 happens-before 于线程 B 中的任意操作。
  6. join()规定:如果线程 A 执行操作 ThreadB.join()并胜利返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作胜利返回

3. 程序一致性模型与 JMM 的区别

  • 程序一致性模型是强内存模型,JMM 是弱内存模型
  • 程序一致性模型保障单线程内的操作按程序的程序执行,JMM 不保障(即便程序正确同步,临界区中的代码能够重排序)

  • (不论程序是否同步)程序一致性模型中,所有线程都只能看到同一个的操作执行程序(JMM 不保障,因为 JMM 中线程在本地内存批改数据,在刷新到主内存之前,其余线程不可见)
  • 程序一致性模型保障对内存的读写操作都是原子的,JMM 不保障对 64 位 double 或 long 数据的读写是原子的
退出移动版