深入java内存模型 -Java Memory Model(JMM)

Java Memory Model

        java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存中和从内存中取出变量的这样底层的细节。此处的变量(Variables)与java编程中所说的变量有所区别,它包括了示例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然也就不会存在竞争问题。为了获得较好的执行效能,java内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即使编译器进行调整代码执行顺序这类优化措施。
        java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了该线程所使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)等都必须在工作内存中进行,而不能直接读主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
        主内存主要对应java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。

在这里插入图片描述

内存间交互操作

        关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之间的实现细节,java内存模型中定义了8中操作来完成,虚拟机实现时必须保证每一种操作都是原子的、不可分割的。

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于至内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从哪个主内存中得到的变量值放入工作内存的副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这操纵。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存中的变量,它把工作内存中一个变量的值传递到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。在这里插入图片描述

        java内存模型规定了在执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、 store和 write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何 assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的変量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或 assign)的变量,换句话说,就是对一个变量实施use、store操作之前必须先执行过了 assign和load操作。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行 unlock操作,也不允许去 unlock一个被其他线程锁定住的变量。
  • 对一个变量执行 unlock操作之前,必须先把此变量同步回主内存中(执行store、wirte操作)。
Java内存模型的特性
原子性(Atomicity)

        由java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store、write,我们大致可以认为基本数据操作类型的访问读写是具备原子性的。(synchronized快之间的操作即是原子性)

可见性(Visibility)

        可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。java内存模式是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是,volatile的特殊规则保证刷新了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。
        除了volatile之外,synchronized和final也能实现可见性。synchronized的可见性是由于“对一个变量执行unlock操作之前,必须先把此变量同步到主内存中”;而final是指被它修饰的字段在构造器中一旦初始化完成,并且构造器没有吧“this”的引用传递出去,那么在其他线程中就能看见final字段的值。

有序性(Ordering)

        如果在本线程内观察,那么所有的操作都是有序的;如果在一个线程中观察另一个线程,那么所有的操作都是无序的。

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

Happen-Before(先行发生规则)

        如果java内存模型中所有的有序性都仅仅靠volatile和synchronized来完成,那么有一些操作将会变得很繁琐,而且这些我们在编写java并发代码中并没有感觉到,是因为有一个“先行发生(happens-before)”的原则。它是判断数据是否存在竞争、线程是否安全的主要依据。意思就是当A操作先行发生于B操作,则在发生B操作的时候,操作A产生的影响能被B观察到,“影响”包括修改了内存中的共享变量的值、发送了消息、调用了方法等。

Happen-Before的规则有以下几条:

  • 程序次序规则(Program Order Rule):在一个线程内,程序的执行规则跟程序的书写规则是一致的,从上往下执行。
  • 管程锁定规则(Monitor Lock Rule):一个Unlock的操作肯定先于下一次Lock的操作。这里必须是同一个锁。同理我们可以认为在synchronized同步同一个锁的时候,锁内先行执行的代码,对后续同步该锁的线程来说是完全可见的。
  • volatile变量规则(volatile Variable Rule):对同一个volatile的变量,先行发生的写操作,肯定早于后续发生的读操作
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的没一个动作
  • 线程中止规则(Thread Termination Rule):Thread对象的中止检测(如:Thread.join(),Thread.isAlive()等)操作,必行晚于线程中所有操作
  • 线程中断规则(Thread Interruption Rule):对线程的interruption()调用,先于被调用的线程检测中断事件(Thread.interrupted())的发生
  • 对象中止规则(Finalizer Rule):一个对象的初始化方法先于一个方法执行Finalizer()方法
  • 传递性(Transitivity):如果操作A先于操作B、操作B先于操作C,则操作A先于操作C
重排序

       在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。
       从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序。

  1. 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序:处理器将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序:处理器使用缓存和读/写缓冲区,使得加载和存储操作看上去可能实在乱序执行。

重排序需要遵守一定的规则

       重排序遵守数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这2个操作就存在数据依赖性。存在数据依赖性的操作,不可以重排序。数据依赖性只是针对单个处理器中执行的指令序列和单个线程中执行的操作。
       重排序遵守as-if-serial操作:就是说不管怎么重排序,单线程程序的执行结果都不会改变。
       但是重排序也会带来一些问题,导致多线程程序出现可见性和有序性的问题。

as-if-serial语义

       as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。

       as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值