总线锁
操作系统提供了总线锁机制。前端总线(也叫CPU总线)是所有CPU与芯片组连接的主干道,负责CPU与外界所有部件的通信,包括高速缓存、内存、北桥,其控制总线向各个部件发送控制信号,通过地址总线发送地址信号指定其要访问的部件,通过数据总线实现双向传输。在CPU内核1要执行i++操作的时候,将在总线上发出一个LOCK#信号锁住缓存(具体来说是变量所在的缓存行),这样其他CPU内核就不能操作缓存了,从而阻塞其他CPU内核,使CPU内核1可以独享此共享内存。
每当CPU内核访问L3中的数据时,都会通过线程总线来进行读取。总线锁的意思是在线程总线中加入一把锁,例如,当不同的CPU内核访问同一个缓存行时,只允许一个CPU内核进行读取,如图4-5所示,a、b存储于L3高速缓存中,当CPU内核1对a进行访问时,会在总线上发送一个LOCK#信号,CPU内核2想对b进行查询,但是总线被锁住,得等CPU内核1访问完,CPU内核2才能访问b。在多CPU的系统中,当其中一个CPU要对共享主存进行操作时,在总线上发出一个LOCK#信号,这个信号使得其他CPU无法通过总线来访问共享主存中的数据,总线锁把CPU和主存之间的通信锁住了,这使得锁定期间,其他CPU不能操作其他主存地址的数据,总线锁的开销比较大,这种机制显然是不合适的。
总线锁的缺陷是:某一个CPU访问主存时,总线锁把CPU和主存的通信给锁住了,其他CPU不能操作其他主存地址的数据,使得效率低下,开销较大。
总线锁的粒度太大了,最好的方法就是控制锁的保护粒度,只需要保证被多个CPU缓存的同一份数据一致即可。所以引入了缓存锁(如缓存一致性机制),后来的CPU都提供了缓存一致性机制,Intel 486之后的处理器就提供了这种优化。
缓存锁
为了提高处理速度,CPU不直接和主存进行通信,而是先将系统主存的数据读到内部高速缓存(L1、L2或其他)后再进行操作,但操作完不知道何时会写入内存。如果对声明了volatile的变量进行写操作,JVM就会向CPU发送一条带lock前缀的指令,将这个变量所在缓存行的数据写回系统主存。
但是即使写回系统主存,如果其他CPU高速缓存中的值还是旧的,再执行计算操作也会有问题。所以在多CPU的系统中,为了保证各个CPU的高速缓存中数据的一致性,会实现缓存一致性协议,每个CPU通过嗅探在总线上传播的数据来检查自己的高速缓存中的值是否过期,当CPU发现自己缓存行对应的主存地址被修改时,就会将当前CPU的缓存行设置成无效状态,当CPU对这个数据执行修改操作时,会重新从系统主存中把数据读到CPU的高速缓存中。
因为高速缓存的内容是部分主存内容的副本,所以应该与主存内容保持一致,而CPU对高速缓存副本如何与主存内容保持一致有几种写入模式供选择,主要的写入模式有以下两种:
(1)Write-Through(直写)模式:在数据更新时,同时写入低一级的高速缓存和主存。此模式的优点是操作简单,因为所有的数据都会更新到主存,所以其他CPU读取主存时都是最新值。此模式的缺点是数据写入速度较慢,因为数据修改之后需要同时写入低一级的高速缓存和主存;
(2)Write-Back(回写)模式:数据的更新并不会立即反映到主存,而是只写入高速缓存。只在数据被替换出高速缓存或者变成共享(S)状态时,如果发现数据有变动,才会将最新的数据更新到主存。Write-Back模式的优点是数据写入速度快,因为发生数据变动时不需要写入主存,所以这种模式占用总线少,大多数CPU的高速缓存采用这种模式。此模式的缺点为:实现一致性协议比较复杂,因为最新值可能存放在私有高速缓存中,而不是存放在共享的高速缓存或者主存中;