JAVA内存模型
对于我们平时开发的业务应用来说,内存应该是访问速度最快的存储设备,对于频繁访问的数据,我们总是习惯把它们放到内存缓存中,有句话不是说么,缓 存就像是清凉油,哪里有问题就抹一抹。但是CPU的运算速度比起内存的访问速度还要快几个量级,为了平衡这个差距,于是就专门为CPU引入了高速缓存,频 繁使用的数据放到高速缓存当中,CPU在使用这些数据进行运算的时候就不必再去访问内存。但是在多CPU时代却有一个问题,每个CPU都拥有自己的高速缓 存,内存又是所有CPU共享的公共资源,于是内存此时就成了一个临界区,如果控制不好各个CPU对内存的并发访问,那么就会产生错误,出现数据不一致的情 况。为了避免这种情况,需要采取缓存一致性协议来保证,这类协议有很多,各个硬件平台和操作系统的实现不尽相同。
JVM需要实现跨平台的支持,它需要有一套自己的同步协议来屏蔽掉各种底层硬件和操作系统的不同,因此就引入了Java内存模型。对于Java来说 开发者并不需要关心任何硬件细节,因此没有多核CPU和高速缓存的概念,多核CPU和高速缓存在JVM中对应的是Java语言内置的线程和每个线程所拥有 的独立内存空间,Java内存模型所规范的也就是数据在线程自己的独立内存空间和JVM共享内存之间同步的问题。下面这两张图说明了硬件平台和JVM内存 模型的相似和差异之处。
Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,线程只能访问自己的工作内存,不可以访问其它 线程的工作内存。工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当 中。如何保证多个线程操作主内存的数据完整性是一个难题,Java内存模型也规定了工作内存与主内存之间交互的协议,首先是定义了8种原子操作:
(1) lock:将主内存中的变量锁定,为一个线程所独占
(2) unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量
(3) read:将主内存中的变量值读到工作内存当中
(4) load:将read读取的值保存到工作内存中的变量副本中。
(5) use:将值传递给线程的代码执行引擎
(6) assign:将执行引擎处理返回的值重新赋值给变量副本
(7) store:将变量副本的值存储到主内存中。
(8) write:将store存储的值写入到主内存的共享变量当中。
我们可以看到,要保证数据的同步,lock和unlock定义了一个线程访问一次共享内存的界限,有lock操作也必须有unlock操作,另外一 些操作也必须要成对出现才可以,像是read和load、store和write需要成对出现,如果单一指令出现,那么就会造成数据不一致的问题。 Java内存模型也针对这些操作指定了必须满足的规则:
(1) read和load、store和write必须要成对出现,不允许单一的操作,否则会造成从主内存读取的值,工作内存不接受或者工作内存发起的写入操作而主内存无法接受的现象。
(2) 在线程中使用了assign操作改变了变量副本,那么就必须把这个副本通过store-write同步回主内存中。如果线程中没有发生assign操作,那么也不允许使用store-write同步到主内存。
(3) 在对一个变量实行use和store操作之前,必须实行过load和assign操作。
(4) 变量在同一时刻只允许一个线程对其进行lock,有多少次lock操作,就必须有多少次unlock操作。在lock操作之后会清空此变量在工作内存中原 先的副本,需要再次从主内存read-load新的值。在执行unlock操作前,需要把改变的副本同步回主存。