JUC并发编程与高性能内存队列disruptor实战-下

并发理论 JMM 概述

Java Memory Model缩写为JMM,直译为Java内存模型,定义了一套在多线程读写共享数据时(成员变量、数组)时,对数据的可见性、有序性和原子性的规则和保障;JMM用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各平台下都能够达到一致的内存访问效果。

JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器对代码指令重排序、处理器对代码乱序执行、CPU切换线程等带来的问题。

并发与并行

并发:指的是多个事情,在同一时间段内同时发生了; 并发的多个任务之间是互相抢占资源的。

并行:指的是多个事情,在同一时间点上同时发生了;并行的多个任务之间是不互相抢占资源的。

只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。

现代计算机内存模型

现代计算机处理器与存储设备运算速度完全不在同一量级上,至少相差几个数量级,如果让处理器等待计算机存储设备那么这样处理器的优势就不会体现出来。为了提高处理性能实现高并发,在处理器和存储设备之间加入了高速缓存(cache)来作为缓冲。将CPU运算需使用到的数据先复制到缓存中,让CPU运算能够快速进行;当CPU运算完成之后,再将缓存中的结果写回主内存,这样CPU运算就不用依赖等待主内存的读写操作了。

高速缓存设置为多级缓存,其目的为了解决CPU运算速度与内存读写速度不匹配的矛盾;在CPU和内存之间,引入了L1高速缓存、L2高速缓存、L3高速缓存
每一级缓存中所存储的数据全部都是下一级缓存中的一部分,当CPU需要数据时,先从缓存中取,加快读写速度,提高CPU利用率。存储层次金字塔的结构:

寄存器 → L1缓存 → L2缓存 → L3缓存 → 主内存 → 本地磁盘 → 远程数据库。

越往上访问速度越快、成本越高,空间更小。越往下访问速度越慢、成本越低,空间越大。

image-20220117124047273

每个处理器都有自己的高速缓存,同时又共同操作同一块主内存,当多个处理器同时操作主内存时,可能将导致各自的的缓存数据不一致,为了解决这个问题
主要提供了两种解决办法:

总线锁:在多 cpu 下,当其中一个处理器要对共享内存进行操作的时候,在总线上发出一个 LOCK# 信号,这个信号使得其他处理器无法通过总线来访问到共享内存中的数据,总线锁定把 CPU 和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,总线锁定的开销比较大,这种机制显然是不合适的。总线锁的力度太大了,最好的方法就是控制锁的保护粒度,只需要保证对于被多个 CPU 缓存的同一份数据是一致的就可以了。

缓存锁:相比总线锁,缓存锁即降低了锁的力度。核心机制是基于缓存一致性协议来实现的。为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,常见的协议有 MSI、MESI、MOSI 等,最常见的为Intel的MESI协议是四种状态的缩写,用来修饰缓存行的状态。

M:被修改,该缓存行的数据被修改了,和主存数据不一致。监听所有想要修改此缓存行对应的内存数据的操作,该操作必须等缓存行数据更新到主内存中,状态变成 S (Shared)共享状态之后执行。

E:独享,该缓存行和内存数据一致,数据只在本缓存中;监听所有读取此缓存行对应的内存数据的操作,如果发生这种操作,Cache Line 缓存状态从独占转为共享状态。

S:分享,缓存行和内存数据一致,数据位于多个缓存中,监听其他缓存使该缓存行失效或者独享该缓存行的操作,如果检测到这种操作,将该缓存行变成无效。

I:无效的,该缓存行的数据无效,没有监听,处于失效状态的缓存行需要去主存读取数据。

image-20220115141207580

如何发现数据是否失效?总线的嗅探机制每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

嗅探缺点:总线风暴由于Volatile的MESI缓存一致性协议,需要不断的从主内存嗅探和CAS不断循环,无效交互会导致总线带宽达到峰值。所以不要大量使用Volatile,至于什么时候去使用Volatile什么时候使用锁,根据场景区分。

image-20220116111806943

本地内存与主内存

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzjdzy.html