12 Java内存模型与线程
Java内存模型与线程
硬件的效率与一致性
- 缓存一致性(Cache Conference): 在多路处理器系统中,每个处理器都有自己的高速缓存,他们共享同一主内存(Main Memory),这种系统称为共享内存多核系统(Shared Memory Multiprocessors System)
- 缓存一致性协议: MSI、MESI、MOSI、Synapse、Firefly、Dragon Protocol
- 处理器、高速缓存、主内存间的交互关系
- 乱序执行(Out-Of-Order Execution): 为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致。Java虚拟机的即时编译器中也有指令重排序(Instruction Reorder)优化
Java内存模型
- Java内存模型(Java Memory Model,JMM): 用来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果
主内存和工作内存
- 主内存(Main Memory): Java内存模型规定了所有的变量都存储在主内存中(在物理上为虚拟机内存的一部分)
- 工作内存(Working Memory): 线程的工作内存中保存了该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不同线程直线无法直接访问对方工作内存中的变量,线程间变量的传递均需要通过主内存来完成
- 线程、主内存、工作内存三者的交互关系
- 主内存对应Java堆中的对象实例数据部分,工作内存对应虚拟机栈中的部分区域 (以上均为勉强对应)
内存间交互操作
8种原子性操作:
- lock(锁定): 作用于主内存的变量,把一个变量标识为一条线程独占的状态
- unlock(解锁): 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取): 作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存,以便以后的load动作使用
- load(载入): 作用于工作内存的变量,把read操作从主内存得到的变量值放入工作内存的变量副本中
- use(使用): 作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
- assign(赋值): 作用于工作内存的变量,把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
- store(存储): 作用于工作内存的变量,把工作内存中一个变量的值传送到主内存中,以便以后的write操作使用
- write(写入): 作用于主内存的变量,把store操作从工作内存中得到的变量值放入主内存的变量中
把一个变量从主内存拷贝到工作内存: 按顺序执行 read 和 load 操作
把一个变量从工作内存同步回主内存: 按顺序执行 store 和 write 操作执行操作时需要满足的规则:
- 不允许
read 和 load
、store 和 write
操作之一单独出现,即不允许一个变量从主内存读取但工作内存不接受,或者工作内存发起回写但是主内存不接受 - 不允许一个线程丢弃它最近的 assign 操作,即变量才工作内存中改变后必须把该变化同步回主内存
- 不允许一个线程无原因的(没有发生任何 assign 操作)把数据从线程的工作内存同步回主内存
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load 或 assign)的变量,即在对一个变量执行 use、store 操作之前,必须先执行 assign 和 load 操作
- 一个变量在同一时刻只允许一条线程对其进行 lock 操作,但 lock操作 可以被同一个线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁
- 对一个变量执行 lock 操作将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行 load 或 assign 操作以初始化变量的值
- 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作,也不允许去 unlock 一个被其他线程锁定的变量
- 对一个变量执行 unlock 操作前,必须先把此变量同步回主内存(执行 store、write 操作)
- 不允许
Java内存模型的操作后来简化为 read、write、lock、unlock 四种
volatile型变量的特殊规则
- volatile关键字: Java虚拟机提供的最轻量级的同步机制,具有两项特性
- 保证此变量对所有线程的可见性(由于Java中的操作运算符并非原子操作,因此volatile变量的运算在并发下一样不安全,volatile只能保证可见性,不能保证原子性)
在不符合以下规则的场景中需要通过加锁保证原子性1. 运算结果并不依赖变量的当前值,或者能够确保只有单一线程修改变量的值
2. 变量不需要与其他的状态变量共同参与不受约束 - 禁止指令重排序优化
- 保证此变量对所有线程的可见性(由于Java中的操作运算符并非原子操作,因此volatile变量的运算在并发下一样不安全,volatile只能保证可见性,不能保证原子性)