JVM之Java内存模型

Java内存模型

Java内存模型(Java Memory Model)JMM,JMM并不是像JVM内存结构一样是真实存在的。他只是一个抽象的概念。

Java内存模型就是一个符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能得到一致效果的机制及规范。目的是解决由于多线程通过共享内存进行通信时,存在的原子性,可见性(缓存一致性)以及有序性问题。

在这里插入图片描述

原子性

线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度,所有在多线程场景下,就会发生原子性问题。因为线程在执行一个读改写操作时,在执行完读改之后,时间片耗完,就会被要求放弃CPU,并等待重新调度。这种情况下,读改写就不是原子操作。即存在原子性问题

缓存一致性

在多核CPU,多线程的场景中,每个核都至少有一个L1缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的cache中保留一个共享内存的缓冲。由于多线程是可以并行的,可能会出现多个线程同时性写各自的缓存的情况,而各自的cache就有可能不同。

在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的各自缓存中,关于同一个数据的缓存内容可能不一致。

有序性

除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱码执行,比如load->add->save有可能会优化成load->save->add。这就是有序性问题。

多CPU多级缓存导致的一致性问题、CPU时间片机制导致的原子性问题、以及处理器优化和指令重排导致的有序性问题都是硬件的不断升级导致。

解决以上问题

所有为了保证并发编程中可以满足原子性、可见性和有序性,就有了内存模型

为了保证共享内存的正确性(原子性、可见性、有序性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它和处理器有关、与缓存有关、与并发有关、与编译器有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了在并发场景下的一致性、原子性、有序性。

针对上面的问题,不同的操作系统都有不同的解决方案,而Java语言为了屏蔽掉底层的差异,定义了一套属于Java语言的内存模型规范,即Java内存模型。

Java内存模型规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存的变量是主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接写在主内存中。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行

而JMM就是作用在工作内存和主存之间数据同步过程。它规定了如何做数据同步以及什么时候做数据同步。

在这里插入图片描述

Java内存模型的实现

Java中提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、concurrent包等。这些其实就是Java内存模型封装了底层的实现后供开发者直接使用。

这里不准备详细介绍关键字的具体用法和实现,只介绍在Java中,分别说用什么方法来保证原子性、有序性、一致性。

原子性

在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现就是由这两个字节码提供的,在Java中对应的关键字就是synchronized。

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

Happens-Before规则

导致可见性的原因是缓存,导致有序性的原因是编译优化。那么我们只要按需禁用缓存和编译优化就可以了。

JAVA推出了JAVA内存模型,JAVA内存模型规范了JVM提供按需禁用缓存和编译优化的方法。具体来说,就是volatile,synchronized,final三个关键字,以及Happens-Before规则

happens-before仅仅要求前一个操作的执行结果对后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens-Before 规则,下面我来介绍这8大规则:A操作happens-before于B操作 == A happens(发生) B before(之前)

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意后续操作。
  2. 监视器锁规则:对于一个锁的解锁,happens-before于随后对于这个锁的加锁。
  3. volatie变量规则:对于一个volatile变量的写操作,happens-before于后续对该变量的读操作。
  4. 传递性规则:如果Ahappen-beford B,且B happen-before C,那么A happen-before C;
  5. 线程start()规则:main主线程启动子线程B后,子线程B能够看到main线程启动子线程B之前的操作。
  6. 线程join()规则:线程A调用线程B的join()方法,线程A等待线程B执行完join()成功返回后,线程B的任意操作都队线程A中B.join()之后可见。
  7. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
  8. 对象终结规则:这个也简单的,就是一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

可见性

Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方法来实现的。

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

synchronized是通过加互斥锁来实现原子性的,JMM关于synchronized的两条规定:

(1) 线程解锁前,必须把共享变量的最新之刷新到主内存中
  (2) 线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要时同一把锁)

我来简单描叙一下线程执行互斥代码的过程:

1、获得互斥锁
    2、清空工作内存
    3、从主内存拷贝变量的最新副本到工作内存
    4、执行代码
    5、将更改后的共享变量的值刷新到主内存
    6、释放互斥锁
  synchronized从而实现类原子性,也具备内存可见性。

有序性

在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值