Java内存模型探秘

1.Java内存模型概述

  Java内存模型是一种抽象概念,不是真实存在的。主要定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存取出变量这样的底层细节。注意这里的变量仅包括实例字段、静态字段、构成数组对象的元素,但不包括局部变量与方法参数。因为后者是线程私有的,不会被共享,自然就不存在竞争问题。

2.主内存与工作内存

  Java内存模型规定了所有的变量都存储在主存中。每条线程还有自己的工作内存,工作内存中保存了该线程使用到的变量的主内存拷贝副本,线程对变量的操作都在工作内存中进行,不能直接读写主存中的变量。不同的线程之间也无法访问对方工作内存中的变量。线程间变量的传递需要通过主内存来完成。

线程、工作内存、主内存,之间的关系如下图:

内存间的交互操作

  即一个变量如何从工作内存同步到主内存,如何从主内存拷贝到工作内存之间的实现细节。

  Java内存模型定义了8中交互动作,虚拟机实现时必须保证每一种操作都是原子的、不可再分的。

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

 如果把一个变量从主内存复制到工作内存,那么就要顺序的执行read、load操作。如果要把变量从工作内存同步到主内存中,就要顺序的执行store、write操作。注意:Java内存模型只要求上述两种操作必须按顺序执行,并没有保证是连续执行。也就是说,read和load、store和write之间是可插入其他指令的,例如对主内存中变量a和b进行访问时,可能出现顺序是read a、read b、load b、load  a。初次之外Java内存模型还规定了在执行上述8中操作时必须保证如下规则。

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

 对于volatile型变量的特殊操作:可以参考这篇博客<volatile关键字解析>

3.原子性、可见性与有序性

  • 原子性:是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。
  • 可见性:指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
  • 有序性:指程序执行的顺序按照代码的先后顺序执行。

4.先行发生原则

  先行发生是指Java内存模型中定义的两项操作之间的偏序关系。如果说操作A先行发生于操作B,也就是说发生操作B之前,操作A产生的影响能被操作B观察到。“影响”包括修改了共享变量的值、发送了消息、调用了方法等。

  下面举个栗子来促进下理解:

//线程A中执行

int i = 1;

//线程B中执行

int j = i;

//线程C中执行

i = 2;

  假设线程A优先于线程B执行,根据先行发生原则,待线程B执行后 j 的值一定等于1,不考虑C的情况。现在假设C介于A、B之间,没有先行发生原则,那么 j 的值我们就无法判断了。因为线程C对i的影响可能会被B观察到也可能不会,这时候线程B就存在读取到过期数据的风险,不具备多线程安全性。

Java模型中一些天然的先行发生关系

  • 程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于在后面的操作。换句话说就是控制流程顺序
  • 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
  • volatile变量规则:volatile变量的写操作先行发生于后面对这个变量的读操作。
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则:线程的所有操作都先行发生于对此线程的终止检测。我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段来检测线程是否终止。
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
  • 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始。
  • 传递性:操作a先行发生于操作b,操作b先行发生于操作c,那么就可以得出操作a先行发生于操作c。

 

posted @ 2018-10-13 22:18  不二尘  阅读(748)  评论(0编辑  收藏  举报