随着cpu由单核变成多核,又有了超线程。所以就会出现这样的问题,多核cpu在各自的缓存处理数据后,当同步数据到同一块主内存时,无法确定以谁的缓存数据为准。所以为了解决cpu缓存一致性的问题,特地制定了一些操作协议,例如MSI、MOSI、Firefly等。而在这些操作协议下,对特定的内存或高速缓存进行读写访问的过程,就是内存模型。不同架构(ARM/X86等)的物理机有不同的内存模型。
为了屏蔽不同架构的机器上的内存访问差异,让Java程序在各种平台下都能达到一致的内存访问效果,所以Java也制定了一套内存操作协议,即Java内存模型。JMM保证了多线程下变量的缓存一致性。
JMM主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节,来实现缓存一致性。JMM在定义上将内存分为主内存和工作内存,主内存对应Heap,属于线程公有,工作内存对应虚拟机栈,属于每个线程私有。
同时JMM对内存中的变量做了以下规定:
既然JMM规定了变量在主内存和工作内存中如何传输和操作,同时也提供了八个原子指令来具体实现细节。
对于上面的八个操作指令,JMM也指定了一些规则:
如何理解这八条规则和八条指令?
指令中的lock/unlock是让一个变量被一个线程独享,其他六个指令都是变量在工作内存中的赋值和传输操作. 规则中最核心的就是第六条。JMM的出现是为了当一个线程对变量修改时,其他线程停止修改,并能获取最新的值来进行操作,从而保证数据的一致性。而lock可以让一个变量只能被一个线程修改,而且让其他线程的工作内存此变量的值失效,想用此变量必须通过指令来获取新的值。
synchronize底层是由monitorenter/monitorexit来实现的,就相当于lock/unlock。在synchronize的作用范围内,只能一个线程进行操作,其他线程工作内存中的变量会失效,当此线程修改完unlock的时候,其他线程会重新加载变量最新值来进行操作,从而保证数据的一致性。volatile关键字也通过内存屏障实现了lock/unlock的功能。
今天看了大冰的《啊 2.0》第一二章,感慨良多。希望我们都可以在各自的地方努力成长。