最近因为小组的分享的缘故,细看了一下JAVA内存模型,猛地发现原来我一直认为应该怎么样怎么样的事情,和实时是不符合的,接下来简单的记录一下最近的感悟。
在JVM 1.2之前,Java的内存模型实现总是直接操作主内存的,多线程的时候访问效率比较低下。目前的JVM,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不直接在主存中进行读写,这样提高了访问效率。但造成一个结果就是如果一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,就产生了数据的不一致。
VM系统中存在一个主内存叫:main memory,JAVA中所有的变量都是存储在这里的,所有变量共享一块内存。每个线程有自己的工作内存,叫:working memory,用来存储各个线程从main memory中的变量的拷贝。线程的所有对变量的操作都是在working memory中完成的,完成后,修改的数据会刷到main memory中。整个结构图如下:
线程之间的通信也是通过于主内存交互完成的(线程本身不进行通信)
重排序
解决的方式:将cache分片,即将一块 cache 划分成互不关联地多个 slots ( 逻辑存储单元,又名 Memory Bank 或 Cache Bank) , CPU 可以自行选择在多个 idle bank 中进行存取。这种 SMP 的设计,显著提高了 CPU 的并行处理能力,也回避了 cache 访问瓶颈。
举个例子:a=1;b=2;
理想情况下,执行的顺序应该是:
cpu0->写入a=1到cache1中//A
cpu0->写入b=2到cache2中//B
如果cache1状态为忙碌的时候,cache0则需要等待
如果做过重排序的话就可以先执行B过程,这样就不会有cache wait,性能在这块就好有很好的保证
数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。如下面:
写后读,读后写,写后写
这三种情况只要重排后,程序的执行结果将会改变,所有编译器是不会对这几种情况进行重排序的
说到底重排序遵循的规则就是:不管怎么排序,程序的执行结果不会改变(这正是as-if-serial的语义)
happen-before
语义:如果A行为发生于B行为之前,那么B行为对A行为可见
遵循的规则:
程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。