JMM内存模子

藏宝库编辑 2024-10-2 17:08:27 22 0 来自 中国
什么是JMM内存模子

内存模子可以明白为在特定的操纵协议下,对特定的内存或高速缓存举行读写访问的过程抽象描述,差别架构下的物理机拥有不一样的内存模子。
JMM(Java内存模子)源于CPU架构的内存模子(用于办理多处置处罚器架构系统中的缓存同等性题目)。JVM为了屏蔽各个硬件平台和操纵系统对内存访问机制的差别化,提出了JMM概念。因此它不是对物理内存的规范,而是在虚拟机底子上举行的规范从而实现平台同等性。
Java内存模子(Java Memory Model,JMM)是一种抽象的概念,它描述的是一组规则或规范,通过这组规范界说了步伐中各个变量(包罗实例字段、静态字段和构成数组对象的元素)的访问方式。
JMM布局规范

JMM规定了全部的变量都存储在主内存(Main Memory)中。每个线程尚有本身的工作内存(Working Memory),线程的工作内存中生存了该线程使用到的变量的主内存的副本拷贝,线程对变量的全部操纵(读取、赋值等)都必须在工作内存中举行,而不能直接读写主内存中的变量。差别的线程之间也无法直接访问对方工作内存中的变量,线程之间值的转达都必要通过主内存来完成
主内存&工作内存

主内存

重要存储的是Java实例对象,全部线程创建的实例对象都存放在主内存中(除开开启了逃逸分析和标量更换的栈上分配和TLAB分配),不管该实例对象是成员变量照旧方法中的本地变量,也包罗共享的类信息、常量、静态变量。由于是共享数据地区,多条线程对同一个变量举行非原子性操纵时大概会发生线程安全题目
工作内存

重要存储当火线法的全部本地变量信息(工作内存中存储这主内存中的变量副本拷贝),每个线程只能访问本身的工作内存,即线程中的本地变量对其他线程是不可见的,就算是两个线程实行的是同一段代码,它们也会各安闲本身的工作内存中创建属于当火线程的本地变量,固然也包罗字节码行号指示器、干系Native方法的信息
交互协议

八大原子操纵
主内存与工作内存之间的具体交互协议,Java内存模子界说了八种操纵来完成:

  • Lock(锁定)
    作用于主内存的变量,把一个变量标志为一条线程独占状态
  • Read(读取)
    作用于主内存的变量,把一个变量从主内存传输到线程的工作内存中
  • Load(加载)
    作用于工作内存的变量,把Read操纵从主内存中得到的变量值放入工作内存的变量副本中
  • Use(使用)
    作用于工作内存的变量,把工作内存中一个变量的值转达给实行引擎,每当虚拟时机到一个必要使用变量的值的字节码指令时将会实行这个操纵
  • Assign(赋值)
    作用于工作内存的变量,把一个从实行引擎罗致到的值赋给工作内存的变量,每当虚拟时机到一个给变量赋值的字节码指令时实行这个操纵
  • Store(存储)
    作用于工作内存的变量,把工作内存中的一个变量的值转达到主内存中
  • Write(写入)
    作用于主内存的变量,把Store操纵从工作内存中得到的变量的值放入主内存的变量中
  • Unlock(解锁)
    作用于主内存的变量,把一个处于锁定状态的变量开释出来,开释后的变量才可以被其他线程锁定

2.png
同步原则

  • 不允许一个线程无缘故起因地(没有发生过assign操纵)把数据从工作内存同步回主内存中
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即对一个变量实行use和store之前,必须先实行assign和load操纵
  • 如果对一个变量实行lock操纵,将会清空工作内存中此变量的值,在实行引擎使用这个变量之前必要重新实行load或assign操纵初始化变量的值
  • 如果一个变量事先没有被lock操纵锁定,则不允许对它实行unlock操纵;也不允许去unlock一个被其他线程锁定的变量
  • 对一个变量实行unlock操纵之前,必须先把此变量同步到主内存中(实行store和write操纵)
与JVM内存布局的区别

JVM内存布局和JMM内存模子是完全两个差别的概念。
JVM内存布局是处于Java虚拟机层面的,是运行时对Java历程占用的内存举行的一种逻辑上的分别,通过差别数据布局来对申请的内存举行差别使用。对操纵系统来说,本质上JVM照旧存在于主存中
JMM是Java语言与OS和硬件架构层面的,本质上JMM并不能说是某种技能实现,而是一种在多线程并发情况下对于共享变量读写的规范。JMM屏蔽了差别操纵系统差别,是跨平台可用的内存模子,用来描述线程的数据在何时从主内存读取,何时写入主内存,办理线程间数据共享和转达的题目
3.png OS与JVM线程关系

Java线程的实现是基于一对一的线程模子。所谓一对一模子,实际上就是通过语言级别层面步伐去间接调用系统内核的线程模子。
我们在使用Java线程时,如new Thread(Runnable);,JVM内部是调用当前操纵系统的内核线程来完成当前Runnable使命。我们编写的多线程步伐属于语言层面的,步伐一样寻常不会直接去调用内核线程,而是创建一个应用线程映射到一个内核线程,然后通过该线程调用内核线程,进而由操纵系统内核将使命映射到各个处置处罚器。这种应用线程与内核线程间一对一的关系就称为Java步伐中的线程与OS的一对一模子。


三大特性

JMM是围绕着并发编程中原子性、可见性、有序性这三个特性来创建的
原子性

原子性指的是一个操纵是不可停止的,即使是在多线程情况下,一个操纵一旦开始就不会被其他线程影响。
根本范例数据的访问多数是原子操纵,long 和 double 范例的变量是64位的,在32位JVM中,32位的JVM会将64位数据的读写操纵分为2次32位的读写操纵来举行,这就导致long、double范例的变量在32位虚拟机中黑白原子性操纵,数据有大概会被粉碎,也就意味着多线程在并发访问的时候是线程非安全的
可见性

一个线程对共享变量做了修改后,其他的线程立即可以或许看到该变量的这种修改
对于串行步伐来说,可见性是不存在的,由于我们在任何一个操纵中修改了某个变量的值,后续的操纵中都能读取这个变量值,并且是修改过的新值。但在多线程情况中,工作内存与主内存同步耽误征象就会造成可见性题目,别的重排序也大概导致可见性题目
有序性

对于一个线程的代码而言,代码的实行总是从前去后依次实行的
在单线程情况下,代码由编码的序次从上往下实行,就算发生指令重排序,由于全部硬件优化的条件都是必须服从 as-if-serial 语义,以是不管怎么排序,都不会且不能影响单线程步伐的实行结果。对于多线程情况,则大概出现乱序征象,由于步伐编译成呆板码指令后大概会出现指令重排序征象,重排后的指令与原指令的序次未必同等(由于指令重排序征象以及工作内存与主内存同步耽误征象导致)。
办理方案

对于原子性题目,JVM提供了对根本数据范例读写操纵的原子性,对于单个变量(包罗64位long和double)可以用volatile关键字来包管读写操纵的原子性,但volatile关键字对多个volatile操纵或类似volatile++这种复合操纵不具有原子性;对于方法级别或代码块级别的原子性操纵,可以使用 synchronized 关键字或Lock锁来包管步伐实行的原子性。
对于工作内存与主内存同步耽误征象导致的可见性题目,可以使用加锁或volatile关键字来办理
对于指令重排序导致的可见性题目和有序性题目,可以使用volatile关键字办理
同时,JMM内部还界说了一套happens-before原则来包管多线程情况下两个操纵间的原子性、可见性以及有序性
as-if-serial语义

无论什么语言,只要操纵之间没有数据依赖性,编译器和CPU都可以恣意重排序,由于实行结果不会改变,代码看起来就像是完全串行地一行行重新实行到尾,这就是as-if-serial语义
对于单线程来说,编译器和CPU大概做了重排序,但开发者感知不到,也不存在内存可见性题目
对于多线程来说,线程之间的数据依赖性太复杂,编译器和CPU没有办法完全明白这种依赖性并据此做出最合理的优化。编译器和CPU只能包管每个线程的 as-if-serial 语义,线程之间的数据依赖和相互影响,必要上层来确定。
happens-before原则

从JDK5开始,Java使用新的JSR-133内存模子。JSR-133提出了 happens-before 概念,通过这个概念来论述操纵之间的内存可见性。
happens-before 表达的是:前一个操纵的结果必要对后续操纵是可见的。这里提到的两个操纵既可以是在一个线程内,也可以是差别线程之间。
class VolatileExample {  int x = 0;  volatile boolean v = false;  public void writer() {    x = 42;    v = true;  }  public void reader() {    if (v == true) {      //x的值是多少呢?    }  }}

  • 步伐序次规则
    在一个线程中,按照代码的序次,前面的操纵happens-before于反面的恣意操纵
    比方:赋值操纵 x = 42 先于 v = true 实行
  • volatile变量规则
    对一个volatile变量的写操纵happens-before与后续对这个变量的读操纵
  • 转达规则
    如果 A happens-before B,并且 B happens-before C,则 A happens-before C
    "x=42" happens-before 写变量 "v=true"。(步伐序次规则)
    写变量"v=42" happens-before 读变量"v==true"。(volatile变量规则)
    根据转达性规则,得到结果 "x=42" happens-before 读变量"v==true"


  • 监督器锁规则
    对一个锁的解锁 happens-before 于随后对这个锁的加锁
    线程A实行完代码后x的值会酿成12,线程B进入代码块,可以或许看到线程A对x的写操纵,也就是线程B可以或许看到x==12
synchronized (this) { // 此处主动加锁    // x是共享变量,初始值=10    if (this.x < 12) {        this.x = 12;    }} // 此处主动解锁

  • 线程启动规则
    如果线程A调用线程B的start()方法来启动线程B,则start()操纵happens-before于线程B中的恣意操纵
    线程A启动线程B之后,线程B可以或许看到线程A在启动线程B之前的操纵,在线程B中访问到x变量的值为100
// 在线程A中初始化线程BThread threadB = new Thread(() -> {    // 此处的变量x的值是多少呢?答案是100});// 线程A在启动线程B之前将共享变量x的值修改为100x = 100;// 启动线程BthreadB.start();

  • 线程闭幕规则
    线程A等待线程B完成(在线程A中调用线程B的join()方法实现),当线程B完成后,线程A可以或许访问到线程B对共享变量的操纵
Thread threadB = new Thread(() -> {    // 在线程B中,将共享变量x的值修改为100    x = 100;});// 在线程A中启动线程BthreadB.start();// 在线程A中等待线程B实行完成threadB.join();// 此处访问共享变量x的值为100

  • 线程停止规则
    对线程interrupt()方法的调用happens-before于被停止线程的代码检测到停止变乱的发生
// 在线程A中将x变量的值初始化为0private int x = 0;public void execute() {    // 在线程A中初始化线程B    Thread threadB = new Thread(() -> {        // 线程B检测本身是否被停止        if (Thread.currentThread().isInterrupted()) {            // 如果线程B被停止,则此时x的值为100            System.out.println(x);        }    });    // 在线程A中启动线程B    threadB.start();    // 在线程A中将共享变量x的值修改为100    x = 100;    // 在线程A中停止线程B    threadB.interrupt();}

  • 对象闭幕规则
    一个对象的初始化完成happens-before于它的finalize()方法的开始
public class TestThread {    public TestThread() {        System.out.println("构造方法");    }    @Override    public void finalize() throws Throwable {        System.out.println("对象烧毁");    }    public static void main(String[] args) {        new TestThread();        System.gc();    }}运行结果
构造方法
对象烧毁
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:53, Processed in 0.153717 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表