jmm内存模型_并发基础理论:Java内存模型JMM

JMM是干什么的

回顾之前的两篇文章,第一个是聊到缓存可见性导致的并发问题,为了解决缓存可见性问题所以有了缓存一致性协议MESI,但是MESI的同步等待机制会影响性能,所以用了store Buffer来优化,但是优化过后还是会在一些场景存在可见性问题。这个时候又不能单方面的放弃优化,所以就提供一套内存屏障些指令来让我们的开发人员可以根据自己的场景来决定什么是需要禁用CPU缓存优化来避免可见性问题。

第二个问题是指令重排序的场景也会造成并发问题,虽然CPU可以通过as-if-serial原则来区分什么时候可以进行重排序,什么时候不可以进行重排序,但是as-if-serial原则在多线程场景是行不通的,因为CPU没办法通过指令来辨别多线程中的指令逻辑依赖,所以这个时候CPU和编译器它们也提供了屏障指令来让我们的开发人员可以根据自己的场景来决定什么是需禁止重排序来避免重排序可能导致的并发问题。

由于不同的CPU架构的缓存体系不一样,指令重排序的策略不一样,所提供的内存屏障指令也就有差异,所以在Java中为了简化开发人员的工作,避免开发人员需要对底层的系统原理理解过分的依赖,所以封装了一套规范,把这些复杂的指令操作与开发人员编码隔离开来,这套规范就是”Java内存模型“。

Java 内存模型实际上就是规范了 JVM 如何提供按需禁用缓存和重排序优化的方法。其核心就包括volatile、synchronized 和 final 三个关键字,以及几项Happens-Before 规则。有了JMM 作为java的开发人员只需要使用几个关键字(sychronized,volatile,final) ,并且理解几个happens before规则,就能根据自己的需要来禁用缓存优化和指令重排序,从而避免并发问题。

Happens Before 原则

作为开发人员来说,如果不想太深入底层去了解计算机底层的原理,又想编写出正确的并发程序,那么就必须对Happens Before原则加以理解,理解这些原则才能帮助我们避免并发程序的BUG,在出现并发问题后也能马上发现问题的所在。Happens Before原则就像我们的JAVA并发程序的开发手册,这个手册中一共包含了X条的原则,下面我们来一一了解。

规则一:程序顺序原则

定义:在一个线程中,按照程序代码的执行流顺序,先执行的操作happen—before后执行的操作。

说明:这个规则的意思就是 前面的写操作对于后面的读操作来说是可见的。,按下面的代码来说,x=4的写入对于flag=true是可见的。

int 

规则二:volatile变量规则

定义:对一个volatile变量的写操作happen—before后面对该变量的读操作。

说明:这个规则的是说,如果一个线程先修改了volatile的变量,那么这个操作对于后续其他线程对这个volatile变量读操作是可见的。 如下代码,线程1调用write() 修改了共享变量 x,然后线程2调用了read() 读取x,这个时候线程1 操作 x=1 对于线程2是可见的。

volatile  

规则三:管程中锁的规则

定义:一个锁的unlock(解锁)操作happen—before后面对该锁的lock(加锁)操作。

说明:如果线程1解锁了A对象,然后线程2对A进行了加锁操作,那么线程1对共享变量的所有写操作对于线程2是可见的。

这个逻辑对应到代码里面就如下, write()方法 synchronized 代码块执行完之后(也就是对this对象的解锁操作,synchronized代码执行完自动解锁)的结果,对于 read()方法 进入synchronized 代码块(也就是对this对象的加锁操作)是可见的,也就是 代码2 会看到 x=x+1 的结果。

int 

规则四:线程启动规则

定义:Thread.start()方法happen—before调用用start的线程前的每一个操作。

说明:如果A线程调用 B线程的start()方法,那么A线程 在调用B.start()之前对共享变量的所有写操作对于B线程来说都是可见的。

如下代码,当先运行的线程为线程A,A线程先对共享变量v进行赋值,然后A线程调用B线程start()方法,那么B线程是可以看到v=10的这个操作的。

Thread 

规则五:线程终止规则

定义:线程中的所有操作都happen-before于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

说明:以join()为例,如果A线程调用B线程的Join()方法,那么当B线程的Join方法返回后,A线程可以看到B线程对共享变量的所有写操作。 以下面代码为例,当前线程调用了threadB的join()方法并返回后,线程设置x=1的操作对于当前线程是可见的。

int 

规则六:线程中断规则

定义:对线程interrupt()的调用 happen—before 发生于被中断线程的代码检测到中断时事件的发生。

说明:线程A调用了线程B的interrupt()方法,那么当线程B触发interrupt之后,线程A对所有共享变量的写操作对于线程B来说都是可见的。

int 

规则七:对象终结规则

定义:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。

说明:调用对象finalize()方法时,对象初始化完成的任意操作,对于调用finalize()线程来说都是可见的。

public 

规则八:传递性规则

定义:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。

传递性规则要与其他规则组合理解,以volatile规则+传递性规则为例,下面代码中

1、因为 y=1 hanpen-before x=2(顺序性原则)

2、而 x = 2 hanpen-before x = 3(volatile变量 规则);

3、 而x = 3又 hanpen-before z=4(顺序性原则)。

4、所以最终得出 y=1 happen-before z=4(传递性原则);

int 

JMM的实现逻辑

JMM因为涉及到多种概念和底层技术,对于开发人员来说它是一个模糊的存在,在学习的时候会有似懂非懂的感觉,这里我整理了一张逻辑结构图,也许能帮助你理解整个开发人员、JMM体系、底层计算机系统原理的联系。

1、java开发人员可以通过 volatile、synchronized 和 final 三个关键字来禁用缓存优化和重排序优化。

2、 JMM根据happens-Before原则来保证这些关键字的特性。

3、而这几条heppens-Before原则的实现,JVM是通过操作系统的内存屏障指令来实现。

4、最终在操作系统层面通过内存屏障指令来禁用缓存优化和禁止指令重排序来解决缓存可见问题和指令重排序问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值