java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,工作内存保存了被该线程所使用到的变量的主内存副本拷贝,线程所有操作都在工作内存中进行,不能直接读写主内存,包括volatile变量。
java内存模型定义了内存间数据的交互8个操作,这些操作都是原子的:
lock:作用于主内存变量,把一个变量标识为一条线程独占
unlock:作用于主内存变量,把一个变量释放锁定状态
read:作用于主内存变量,把主内存中的变量传输到线程的工作内存,为以后的load使用
load:作用于工作内存,把read操作读取的主内存变量放入工作内存的变量副本中
use:作用于工作内存变量,把工作内存的变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时都会执行这个操作
assign:作用于工作内存变量,把从执行引擎获取到的数据赋给工作内存变量,每当虚拟机需要一个赋值的字节码,都会执行这个操作
store:作用于工作内存变量,把工作内存中的变量的值传递到主内存中,为以后的write使用
write:作用于主内存的变量,把store操作从工作内存获取的变量的值放到主内存的变量中
也就是说,把主内存中的数据读取到工作内存需要顺序执行read->load,把工作内存数据写回到主内存需要顺序执行store->write
只保证执行顺序,但是不保证连续,指令之间可以插入其他指令,除此之外,还有一些其他规则:
不允许read和load、store和write操作之一单独出现,不允许从主内存读取但是工作内存不接受,也不允许工作内存发起写回但是主内存不接受的情况。
不允许一个线程丢弃他的最近的assign操作,即变量在工作内存改变之后必须要同步回主内存
不允许线程无原因(没有发生过assign操作)的把数据从工作内存同步回主内存
变量必须在主内存中创建,不允许再工作内存中直接使用未被初始化的变量,即未执行assign和load,在use和store之前一定进行过assign和load。
一个变量同一时刻只能被一个线程lock,可以被一个线程lock多次,必须要unlock相同次数才会解锁
如果对一个变量lock,会清空工作内存中此变量的值,在执行引擎使用时,必须要重新执行load和assign操作
如果一个变量没有lock,不允许进行unlock,也不允许unlock其他线程lock的变量
对一个变量unlock之前,必须要把变量同步回主内存,即store、write操作
volatile变量对所有线程可见,对volatile变量所有的写操作都能立刻反映到其他线程中,但是volatile变量并不是线程安全的。
volatile变量只能保证在读取时,读取到的一定是内存中最新的值,但是并不能保证线程对其操作的同步。
只有满足如下两个规则,才可以保证同步:
1、运算结果并不依赖与变量的当前值,或者能够保证只有单一的线程修改变量的值。
2、变量不需要与其他的状态变量共同参与不变约束。
volatile禁止指令重排序优化,在增加了volatile修饰之后,赋值之后增加了一个内存屏障,指令重拍不允许把后面的指令重排到内存屏障之前。并且会使得本cpu的cache写入内存,也使别的cpu或别的内核无效化其cache,相当于对cache中的变量增加了store、write操作。
volatile变量的特殊规则:
1.user操作必须在load操作之后,load操作必须在user操作之前。也就是说使用变量前一定要先同步内存中的值到本地存储中。
2.assign操作必须在store操作之前,store操作必须在assign操作之后。也就是说赋值操作之后一定会把数据同步到本地存储中。
3.两个线程分别操作时,一定会保证代码的执行顺序与程序的顺序相同。先读的先同步内存数据,先复制的先回写内存数据。
对于long和double变量,虽然读写分为了两次32位的操作,虽然不满足原子性,但是虚拟机可以把这些操作实现为具有原子性的操作,所以一般情况下我们不需要把long和double设置为volatile的。
java内存模型的三个特性:
原子性:原子性的变量操作load,read,store,write,use,assign。如果需要更大范围的原子性,需要用到lock和unlock,字节码层次是monitorenter和monitorexit来隐式使用这两个操作,在代码层次就是synchronized关键字。
可见性:可见性指一个线程修改了变量的值,其他线程可以立即获得这个修改。在java中都是先把值存储到线程工作空间,再同步到内存中,volatile保证了修改可以立即同步到内存,使用前立即从内存刷新,普通变量无法保证。在java中还有final和synchronized关键字可以保证。synchronized是因为“unlock操作前一定到先把数据同步到内存中,即执行store,write操作”这条规则保证的。final是如何保证的,没有理解,记为一个问题,后续继续学习弄明白;
有序性:java使用volatile和synchronized来实现,volatile是因为本身就禁止了指令重拍,synchronized是因为同一时间只有一个线程可以对变量进行lock操作来保证。
java中happens-befor原则:
1程序次数规则:在一个线程内,按照程序代码顺序
2管城锁定规则:一个unlock操作先行发生于其后对同一个锁的lock操作
3volatile原则:对一个volatile变量的写操作先行发生与其后的读操作
4线程启动原则:Thread的start()方法先行发生于此线程每一个动作
5线程终止规则:线程中的所有操作都先行发生于对线程的终止检查
6线程中断规则:对线程interrupt()操作先行发生于被中断线程的代码检测到中断事件发生,Thread.isterrupted()可以获取到线程是否有中断发生。
7对象终结规则:一个对象的初始化先行发生于他的finalize方法执行
8传递性规则:操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C