Java内存模型(JMM)

什么是Java内存模型(抽象的)

       我们知道,Java程序是需要运行在Java虚拟机上面的,Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

       java内存模型的主要目标是定义程序中各个变量的访问规则。此处变量与java编程所说的变量有所区别,它包括了实例字段、静态字段和构成数组的对象,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享。

      Java的内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java内存模型定义了线程和主内存之间的抽象关系,具体如下:

  1. 共享变量存储于主内存中,每个线程都可以访问。
  2. 每个线程都有私有的工作内存或者称为本地内存。
  3. 工作内存只存储该线程对共享变量的副本。
  4. 线程不能直接操作主内存,只有先操作工作内存之后才能写入主内存。
  5. 工作内存和java内存模型一样也是一个抽象的概念,他其实并不存在。

其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这写交互!

 

内存间交互操作

8种操作

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。
  • unclock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎。
  • assign(赋值):作用于工作内存的变量,把执行引擎接收到的值赋给工作内存的变量。
  • store(存储):作用于工作内存的变量,把工作内存中一个变量的值传送给主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

操作的规定

  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存中读取了但工作内存不接受,或者工作内存发起了回写但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步到主内存。
  • 不允许一个线程无原因地(没有发生任何assign操作)把数据从线程的工作内存同步到主内存。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量use、store操作之前,必须先执行过了assign和load操作。
  •  一个变量在同一时刻只允许一个线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unclock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,那么会清空工作内存次变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果对一个变量没有被lock锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unclock之前,必须先把此变量同步回主内存中(执行store、write操作)。

Java内存模型对并发特征的保证

1、原子性:

       由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个,我们大致可以认为基本数据类型的访问读写是具备原子性。JMM只保证了基本读取和赋值的原子性操作

       如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,这两个字节码指令反映到Java代码中就是同步快-synchronized关键字,因此synchronized块之间和Lock的操作具备原子性。volatile关键字不具备保证原子性的语义。

2、可见性

        在多线程的环境下,如果某个线程首次读取共享变量,则首先从主内存中获取该变量,然后次年入到工作内存中,以后只需要在工作内存中读取该变量即可。如果对该变量执行了修改操作,则先将新值写入到工作内存中,然后在刷新到主内存中。但是什么时候刷新至主内存式不太确定的。      

       可见性就是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

       除了volatile之外,Java还有三个关键字能实现可见性,它们是synchronized和final。同步快的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这条规则获得的,而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到初始化了一半的对象),那么在其他线程中就能看见final字段的值。

3、有序性(Ordering)

       在Java的内存模型中,允许编译器和处理器对指令进行重排序,在单线程情况下重排没有什么影响,但是在多线程的情况下, 重排序会影响到程序的正确运行。重排序需要遵守happens-before规则,不能说你想怎么排就怎么排,如果那样岂不是乱了套。  

       Java语言提供了volatile、synchronized和Lock(显式锁)三个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值