java内存模型规范
复习jdk1.7虚拟机规范
了解java内存模型,需要我们对计算机硬件体系有一个基本掌握,要知道计算机里面的内存、高速缓冲、处理器、内存一致协议这些概念。
硬件系统内存模型
现代计算机为了解决处理器与存储设备之间性能冲突,引入了高速缓存,高速缓存读写速度接近处理器运算速度,为处理器单独使用。将运算需要使用到的数据复制到缓存种,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就不需要等待内存的读写了。
而在多处理器系统中,就涉及到缓存一致性问题,每个处理器都有自己的高速缓存,而它们又共享同一主内存。
内存模型:在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程。
java内存模型
不同的物理架构可以拥有不一样的内存模型,而java内存模型就是为了解决各种物理机上内存访问差异而产生的,以实现让java程序在各种平台下达到一致的内存访问效果。
java内存模型的目标,是定义程序中各个变量的访问规则,即在虚拟机中将变量的存储到内存和从内存中取出变量这样的底层细节。
java内存模型规定所有的变量都存储在主内存。
线程拥有自己的工 作内存,保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作必须在工作内存中完成,不能直接操作主内存。
不同的线程不能直接访问对方的工作内存中变量,线程间变量值传递必须通过主内存来完成。
java内存的8种操作
虚拟机定义内存模型中的8种操作都是原子的,不可再分的。
- lock:作用于主内存的变量,把一个变量标记为一条线程独占的状态。
- unlock:作用于主内存的变量,把一个标记为线程独占状态的变量释放出来,可以被其他线程锁定。
- read:作用于主内存的变量,把一个变量从主内存中传输到线程的工作内存中,随后执行load操作。
- load:作用于工作内存的变量,把read操作的变量放入工作内存的变量副本中。
- use:作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,使用变量值的字节码指令。
- assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存的变量,变量赋值字节码指令。
- store:作用工作内存的变量,把工作内存的变量传输到主内存中,随后执行write操作。
- write:作用于主内存的变量,把store操作的变量值放入主内存的变量中。
内存8种操作的约束规则
- 不允许read和load、store和write操作之一单独出现。
- 不允许一个线程丢弃它的最近的assign操作。
- 不允许一个线程无原因地吧数据从线程的工作内存同步回主内存。
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中使用一个未被初始化的变量。
- 一个变量同一时刻只能被一个线程进行lock操作,但可以被同一个线程重复执行多次,执行lock操作后必须执行相同次数的unlock操作。
- 如果一个变量执行了lock操作,将会清空工作内存中次变量的值,执行引擎使用前需要重新初始化变量的值。
- 如果一个变量事先没有被lock操作,就不允许被unlock操作,也不允许去unlock一个被其他线程锁住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中。
volatile关键字规则
变量定义为volatile后具有两个特性:
- 此变量对所有线程可见,指当一个线程修改此变量的值,其他线程会立即得知。
volatile变量在并发下是不安全的,不能保证原子性,因为java虚拟机执行的是字节码指令,而一个java语句可能被编译成多个字节码指令,而一条字节码在执行引擎中也可能是多行代码执行的。
如“a++”语句生成4条字节码指令,如下:
getstatic
iconst_1
iadd
putstatic
- 禁止指令重排序优化。
指令重排序,是cpu采用了允许将多条指令不按程序规定的顺序分开发送给相应的电路单元处理,同时cpu保证了指令依赖情况以保证执行结果的正确。
这种方案会提升处理器的性能,很多机器有这样陪着能力。volatile关键字禁止了这种能力,是通过lock来实现的。
特别的变量long和double
虚拟机并没有强制约束64位long和double数据类型的load、store、read、write具有原子性,它被当作两个32位的操作来进行。
虚拟机规范中强烈建议虚拟机实现中将它们实现为原子操作,几乎商用虚拟机都是实现称为原子操作,所以也不用担心。
先行发生原则
时间先后顺序和先行发生原子之间基本没有关系,衡量并发安全问题的时候不要受时间顺序的干扰,一切必须以先行发生原则为准。
本线程内,所有的操作都是有序的;一个线程内观察另外一个线程,所有的操作都是无序的。