Java的内存模型:基于线程的内存模型
JMM 定义的主要目标:为了定义程序中各个变量的访问规则,也就是如何将变量从内存中取出来,怎样将它写入。
这里的变量是实例字段、静态字段和数组元素(线程共享)
一、主内存与工作内存。主内存也就是所有内存共享区域(堆、方法区、运行时常量池);工作内存就是每个线程独享的区域。
JMM规定所有变量都必须存储到主内存中,每条线程都有自己的工作内存,其中保存了该线程使用到的变量的主内存副本(也就是说工作内存中使用到的变量是主内存中变量的一个副本)
线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存变量,不同线程之间也无法直接访问彼此的工作内存变量。
二、内存建交互操作(一个变量怎么从主内存拷贝到工作内存,怎么从工作内存同步回主内存)
I.作用于主内存的变量---1.lock(把一个变量标识为一条线程独占的状态)
2.unlock(把一个处于锁定状态的变量释放出来,只有释放了才能被其他线程锁定)
3.read(把一个变量的值从主内存传输到线程的工作内存中)
4.write(把store操作从工作内存中拿到的变量的值放入到主内存的变量中)
作用于工作内存的变量---1.load(把read操作从主内存中中得到的变量值放入工作内存的变量副本中)
2.use(把工作内存中的一个变量值传递给执行引擎)
3.assign(把从执行引擎接收到的值赋值给工作内存的变量)
4.store(把工作内存中一个变量的值传送到主内存中,便于write使用)
II.Java内存模型的三大特征:
1.原子性 :基本数据类型的访问读写是具有原子性的,如:int i=0;和i=3;都是原子操作;
i++就不是原子操作了,展开i=i+1,是两步操作。如果需要大范围的原子性,就需要内建锁或lock体系来支持。
2.可见性:一个线程修改了共享变量的值,其他的线程也能立即得知此修改。volatile、synchronized、final三个关键字可以实现。
3.有序性:在本线程内观察,所有的操作都是有序的;在线程内观察另一个线程,所有的操作都是无序的。
Java内存模型有时候不需要任何手段就能保证到有序性,也叫happens-before原则。
如果两个操作数无法从happens-before原则推导出来,就不能保证他们的有序性,虚拟机可以随意对他们进行重排序。
先行发生原则包括:程序次序规则、锁定规则、volatile变量规则、传递规则、线程启动\中断\终结规则、对象终结规则。
要想并发程序的正确执行,必须同时保证原子性、可见性和有序性。
三、volatile变量的特殊规则
volatile是类型修饰符,保证在多线程中,写操作先行与读操作。
I.保证了此变量对所有线程的可见性,volatile变量在各个线程中是一致的,但无法保证原子性和有序性,所以在并发环境下,volatile变量是不安全的。
在不符合(1.运算结果并不依赖变量当前值,或者能够确保的只有单一线程修改变量的值。2.变量不需要与其他状态变量共同参与不变约束。)
这两条规则的情况下,我们仍然需要添加内建锁或lock来保证原子性。
并发场景下堆类似i++操作如何保证程序运行出正确结果???1、加锁2、使用原子类(java.util.atomic包下所有类内部使用了CAS操作来保证原子性)
II.禁止指令重排。
1.当程序执行到volatile变量的读操作或写操作时,前面的操作一定全部执行了,后面的操作一定没有执行,并且结果已经对后面的操作可见。
2.在指令优化是,不能将在对volatile变量访问的语句放在其后面执行,也不能将volatile后面的语句放在他前面执行。