硬件的效率与一致性:
由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓冲:将需要使用到的数据复制到缓存中,让运算能够快速进行,当运算结束后再从缓存同步回主内存,这样处理器就无需等待缓慢的内存读写。
基于高速缓存的存储交互很好解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,因为引入了一个新的问题:缓存一致性。为解决一直性问题,需要各处理器范围访问缓存时都遵循一些协议,读写时根据协议进行操作,这类协议有:MSI,MESI,MOSI,Synapse,Firefly,及Dragon Protocol等。
内存模型:可以理解为在特定操作系统协议下,对特定的内存或者高速缓存进行读写访问的过程抽象。
Java内存模型
Java虚拟机规范试图定义一种Java内存模型(Java Memory Model JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致性内存访问的效果。
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量的底层细节。
变量包括:实例字段、静态字段和构成数组对象的元素。不包括局部变量与方法参数,因为属于线程私有,不会被共享,不存在竞争问题。
Java内存模型规定了所有的变量都存储在主内存,每条线程还有自己的工作内存,线程的工作内存中保存了主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
Java内存模型定义8种操作:
- lock :作用于主内存的变量,它把一个变量标识为一条线程独占状态。
- unlock:作用于主内存的变量,与lock相反
- read:作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load:作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use:作用于工作内存的变量,它把工作内存中一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign:作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store:作用于工作内存的变量,它把工作内存中的一个变量值传送到主内存中,以便随后的write操作使用。
- write:作用于主内存的变量,它把store操作从工作内存中得到的变量值放入主内存的变量中。
Java内存模型规范8中基本操作规则:
- 不允许read和laod,store和write操作之一单独出现。
- 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变后必须同步回主内存
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量,换句话说就是对一个变量实施use,store操作之前必须先执行过了assign和load
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但是可以重复执行多次,多次执行lock后,只有执行相同次数的unlock才能解锁
- 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量。
- 如果一个变量事先没有被lock锁定,那就不允许对它执行unlock操作
- 对一个变量执行unlock前必须先将此变量同步回主内存中。
volatile型变量特殊规则:
1、可见性:保证此变量对所有线程的可见性,指当一条线程修改这个变量的值,新值对于其他线程来说是可以立即得知的。
volatile应用场景:
a、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
b、变量不需要与其他的状态变量共同参与不变约束。
2、禁止指令重排序:
指令重排序是指cpu采用了允许将多条指令不安程序规定的顺序分开发送给各相应电路单元。但并不是说指令任意重排,cpu需要能正确处理指令依赖情况以保证得出正确的执行结果。
先行发生原则:
- 程序次序规则:线程内书写在前面的操作先行发生于书写后面的操作。
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
- 线程启动规则:Thread的start方法先行于此线程的每一个动作
- 线程终止规则:线程中的所有操作都先发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已终止执行。
- 对象终结规则:一个对象的初始化完成先行于它的finalize()方法。
- 传递性:A先于B,B先于C,则A先于C