单核CPU如果想要再通过提升频率会带来诸多问题凸显,如散热问题,能耗问题,技术难度很高,性能提升遇到瓶颈。改为采用多核cpu架构。
cpu运算速度比主存的存取速度快很多,为了提高处理速度,给每一个核增加高速缓存,cpu从高速缓存读取数据比直接从主存中读取数据快的多。通常多核架构的每个cpu都有他们自己独有的高速缓存:一级高速缓存L1(L1P+L1D),二级高速缓存L2。在同一个cpu芯片上的所有cpu还同时共享一个三级高速缓存L3。这样做的优点是:
1.写缓存区可以保证指令流水线持续运行,可以避免由于cpu停顿下来等待向内存写入数据而产生的延迟。
2.通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总线的占用。
但是,随着多核cpu的使用也出现了一些需要解决的问题,如何保证主存中共享数据的并发问题。这里就出现了并发编程中的三大问题:原子性问题、可见性问题、有序性问题。这篇笔记主要理解一下可见性问题。
可见性:一个线程对共享变量的修改,另一个线程能够立刻可见,我们称该共享变量具备内存可见性。
可见性问题的产生:由于每个线程可能会运行在不同cpu内核中,因此每个线程拥有自己的高速缓存。同一份数据可能会被缓存到多个cpu内核中,在不同cpu内核中运行的线程看到同一个变量的缓存值就会不一样,就可能发生内存的可见性问题。
于是为了保证每个cpu core访问数据的一致性,需要各个cpu在访问高速缓存时遵循一套缓存一致性协议。MESI(Modified、Exclusive、Shared、Invalid)就是一种最常用的缓存一致性协议。共享变量有上述四种状态,共享变量在每种状态下都可能遇到4种操作(本地读、本地写、远程读、远程写)。在每种状态下的操作都遵循MESI协议的规则就能够保证共享变量的可见性。其状态转换图如下:
状态转换规则如下:
在Java中,Java程序是运行在JVM(Java Virtual Machine)中的。JVM中运行Java程序时,在使用内存遵循一套规则,我们叫做JMM(Java Memory Mode)。这一套规则用于解决可见性和有序性问题。
JMM规定给每一个Java线程一个自己私有的工作内存(类比cpu高速缓存),当使用主存(类比cpu的内存)中的变量时,将主存中变量复制到工作内存中。所以这套内存模型与cpu高速缓存模型其实有相似之处,这里就不多讲JMM了。在JVM中和Java语言中包含了JMM的规则指令,将这些指令体现到Java的关键字语义中。
所以在执行Java程序时,只有当共享变量被volatile关键字修饰了,该变量所在的缓存才被要求进行缓存一致性的校验。volatile关键字修饰共享变量后,会在操作该共享变量时添加lock前缀指令,该指令有以下三种功能: