Java内存模型
»深入理解Java虚拟机读书笔记目录:
计算机中的多处理器问题
在计算机中,由于计算机的存储设备与处理器的运算速度相差很大,所以,通常会在处理器和内存中加一个高速缓存来作为缓冲。将运算需要用到的数据复制到缓存中,让运算能快速进行,运算结束后,再将数据从缓存中同步回内存,这样处理器就无须等待缓慢的内存读写。
但是,这就引发了一个新的问题:缓存一致性问题。即在多处理器中,内一个处理器都有自己的一块缓存,但是他们又都共享同一块内存,当多个处理器运算任务都涉及到同一块主存区域时,可能导致各自的数据不一致。为此,各个处理器在访问缓存时需要遵循一些协议。
除了增加缓存之外,为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致。但是,如果存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序不能靠代码的先后顺序来保证。预处理器的优化类似,JAVA虚拟机也有类似的指令重排序优化。
java内存模型
java虚拟机规范中试图定义一种java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致性内存访问效果。
(1) 主内存与工作内存
java内存模型规定所有的变量(这里的变量不包括局部变量和方法参数,因为这两者是属于线程私有)都存储在主内存。每条线程有自己的工作内存,线程的工作内存保存了该线程使用到的变量的主内存的拷贝副本,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存中的变量,不同的线程也无法访问对方的工作内存,线程间变量值的传递需要通过主存来完成。
主内存可以对应Java堆中的对象实例数据部分,工作内存可以对应于虚拟机栈中的部分区域。或者,主内存对应于物理硬件的内存,为了获取更高的运行速度,虚拟机可能会让工作内存优先存储于寄存器和告诉缓存中。
(2) 内存间的交互操作
关于内存之间的交互操作,主要就是将变量从主内存中拷贝到工作内存,然后将工作内存的数据同步回主内存。
为此,java内存模型定义了8种原子操作来完成。
- lock(锁定):作用于主内存中的变量,把一个变量标识为一个线程独占
- unlock(解锁):作用于主内存中的变量,把一个处于加锁的变量释放出来
- read(读取):作用于主内存中的变量,把一个变量从主内存读到工作内存
- load(载入):作用于工作内存中的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中
- use(使用):作用于工作内存中的变量,把工作内存中的一个变量值传递给执行引擎
- assign(赋值):作用于工作内存中的变量,把从执行引擎接收到的值赋给工作内存的变量
- store(存储):作用于工作内存中的变量,把一个变量值传到主存中
- write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把一个变量从主存中复制到工作内存,那么就要顺序执行read 和 load操作,如果要把一个变量从工作内存同步回主存,就要顺序执行store和write
volatile型的变量
关键字volatile是java虚拟机提供的最轻量级的同步机制,但是它大多数情况下并不能保证线程安全。
作用:
- 保证此变量对所有线程的可见性,即,只要是修改了变量,那么能够立即写回主存,并且其他线程用到该变量需要从主存中读取。
- 使用volatile变量的第二个作用是进制指令重排序优化,即不能把后面的指令排到volatile修饰的变量之前。
volatile能够实现线程安全的条件:运算结果并不依赖当前的值,或者确保只有单一的线程修改变量的值;变量不需要与其他的状态变量共同参与不变约束(条件判断?)
原子性、可见性和有序性
(1) 原子性
通常,基本数据类型的访问读写是原子性的,还有Synchronized同步块之间的操作是原子性
(2) 可见性
Java内存模型通过在变量修改后将新值同步回主存,在变量读取前从主存中刷新变量值这种方式来实现可见性。除了volatile还有synchronized和final能够实现可见性。
(3) 有序性
如果在本线程中观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的。可以同volatile和synchronized来实现有序性。