JVM学习笔记(八)-Java内存模型

总体架构

内存模型图

  之所以画成这样,是因为对《深入理解java虚拟机》中提到的内存模型中内存间的交互操作的理解,而不是简单的像书中的图一样。基于执行引擎和主内存的交互方式,以及read、load,store、write操作的含义,我个人认为应该加一个执行引擎和变量值暂存(有可能是寄存器、缓存等)。
  如图所示,每个线程都会有自己的工作内存,在工作时,对共享变量的访问必须先从主内存加载到工作内存,线程对共享变量的读取/赋值等操作都是在工作内存中进行的。对共享变量的修改需要刷新到主内存中才能算做真正完成修改。
  另外,注意这里的共享变量是实例变量、类变量、数组元素等,而不是局部变量和方法参数,因为这两种是属于线程私有的,不存在共享问题。

内存间的交互操作

  关于主内存和工作内存间的交互协议,java内存模型定义了8种基本操作,注意这不是字节码指令。它们都是原子操作(不考虑long、double的非原子协定)

  1. lock:加锁,一个线程lock了共享资源,那么这个共享变量就标识为线程独占状态
  2. unlock:解除共享资源的线程独占状态
  3. read:作用于主内存的变量,将主内存变量的值加载到工作内存中(图中工作内存变量值),为load操作做准备
  4. load:作用于工作内存的变量,将read的值放入工作内存变量副本中
  5. use:作用于工作内存的变量,将变量的值传递给执行引擎,当遇到使用变量的值的字节码指令时会执行这个操作
  6. assign:作用于工作内存的变量,将执行引擎得到的值放入工作内存的变量副本中,当遇到给变量赋值的字节码指令就会执行这个操作
  7. store:作用于工作内存的变量,将变量的值放入主内存中(这个时候还没有放入变量,和工作内存中的变量值存放差不多,有可能是寄存器或缓存中)
  8. write:作用于主内存的变量,将store的值方入变量中

三大特性

原子性

  原子性是指操作的不会被其他线程中断,无论线程间是怎样的步调,这个操作都不会被其他线程影响。

可见性

  可见性指的是一个线程对共享变量的修改可以立即被其他线程知道。

有序性

  如果在本线程内观察,所有操作都是有序的,但是在其他线程看来是无序的。前半句指的是“线程内的串行语义性”,后半句指的是“指令重排序”和“主内存工作内存的同步延迟”带来的影响。

指令重排序

指令重排序类型

  指令重排序的根本目的是优化程序,提高执行效率。在java代码的编译执行过程中,指令重排序共分为两大类、三小类。
两大类:编译器重排序和处理器重排序。
三小类:在处理器重排序中分出指令级并行重排序和内存系统重排序
如图所示:

重排序分类

编译器重排序

  是编译器在不改变单线程的串行语义的前提下,改变语句执行顺序。JMM的编译器重排序规则可以禁止特定类型的编译器重排序。

指令级并行重排序

  指令级并行重排序是处理器优化指令流水线执行的措施。现代处理器的指令执行是流水型的,常见的为五级流水:
取指、译码、计算、访存、回写

指令流水线

  如图,在指令1还没有完成整个操作时,指令2就可以开始进行了,这样可以提高运行速度。然而,这也带来了一些问题,指令流水中断是很麻烦的事情,应该尽量避免。如图所示:

指令流水中断示例

  如图对于A=B+C的指令流水,先从B中取值放入寄存器R1中,在从C中取值放入R2中,然而,在ADD指令的计算阶段,由于时间与C的访存阶段重合,C还没有准备好,所以不得不中断一会儿,然后再进行计算。
下面有一个更复杂的例子:

a=b+c;
d=e-f;
复杂示例

  可以看到会有很多中断。那么接下来看下指令重排序的魅力。

指令重排序

  由于后面的d=e-f对于a=b+c是没有影响的,那么可以将e、f加载到寄存器的指令提前进行,重排序后的结果:

指令重排序结果

  很明显,重排序后不需要插入中断了,这样是不是提高了运行效率咧。

内存系统重排序

  由于处理器使用了缓存和读写缓冲区,常常打乱指令执行顺序。严格意义上来说,我个人认为这更像是一种内存模型带来的副作用
比如下面代码:

处理器A:
a=1;//A1
x=b;//A2
处理器B:
b=2;//B1
y=a;//B2
初始状态:
a=b=0;
指令重排序结果

  上述代码是不是一定执行结果为x=2,y=1;呢,相信有并发编程经验的都知道不一定。这里表面上看,A1先执行,a=1写入到缓冲区中,此时执行A2读取内存的b=0,注意这时,a=1还没有刷新到内存中呢!这样在外界看来,处理器A就是先执行了A2再执行A1的!那么这个改变符合规定吗?答案是当然符合,对于处理器A来说,它把该做的都做了,也没有违反自己的串行语义。如果处理器B也是这样,最后是有可能出现x=y=0这样的结果的!
  然而,个人认为,这个重排序并不带有“主观性”,只是缓存机制带来的副作用而已。
  对于处理器重排序,可以使用内存屏障来禁止,比如volatile,关于volatile和synchronized后面会专门写文章学习。

happens-before

  对于两个操作,有如下情况可以保证它们的顺序性。这里的两个操作可以在同一线程中,也可以在不同线程中。

  1. 程序次序规则:在一个线程中,程序代码中顺序,写在前面的代码次序先于后面
  2. 管程锁定规则:一个unlock操作先于后面对同一个锁的lock操作
  3. 线程启动规则:Thread对象的start()操作先于线程的所有操作
  4. 线程终止规则:线程的所有操作先于对此线程的终止检测
  5. 线程中断规则:interrupt操作先于被中断线程的代码检测到中断事件的发生
  6. 对象终结规则:一个对象的初始化完成先于该对象的finalize()方法
  7. volatile变量规则:volatile变量的写操作先于后面对这个变量的读操作
  8. 传递性:A先于B ,B先于C,则A先于C

总结

  这篇博客讲了内存模型的架构,内存交互操作,JMM三大特性,指令重排序,happens-before规则等JMM内容,此后还会继续学习volatile、final、synchronize的相关内存语义和原理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值