一郎科技|Java多线程_缓存对齐

1.什么是缓存对齐当前的电脑中 , 数据存储在磁盘上 , 可以断电保存 , 但是读取效率较低 。 不断电的情况下 , 数据可以在内存中存储 , 相对硬盘效率差不多是磁盘的一万倍左右 。 但是运算时 , 速度最快的是直接缓存在CPU中的数据 。 CPU有三级缓存分别是L1,L2,L3三级 , CPU访问速度大概是内存的100倍 。
1.1CPU结构对于一台电脑 , 其主板可以支持多少个CPU插槽 , 称为CPU个数 。 对于一颗多核CPU , 单片CPU上集成的处理核心称为CPU核数 。 对于每个核心 , 可以给每个核设置两组寄存器 , 两组pc 。
一郎科技|Java多线程_缓存对齐CPU结构如上图所示(图片来自网络) , 对于一块CPU , 可以有多个处理核心 。 每个核心内有自己的L1,L2缓存 , 多个核心共用同一个L3缓存 。 但一个电脑如果有多个CPU插槽 , 各个CPU有自己的L3 。 对于一个CPU核心来说 , 每个核心都有ALU , 逻辑运算单元 。 负责对指令进行计算 。 Register 寄存器 , 记录线程执行对应的数据 。 PC:指令寄存器 , 记录线程执行到了哪个位置 。 里面存的是指令行数 。 通俗讲 , 就是记录线程执行到了哪一行指令(代码在进入CPU运行前 , 会被编译成指令)了 。
线程在执行的时候 , 将当前线程对应的数据放入寄存器 , 将执行行数放到指令寄存器 , 然后执行过一个时间片后 , 如果线程没有执行完 , 将数据和指令保存 , 然后其他线程进入执行 。 一个ALU对应多个PC|registers的时候(所谓的四核八线程) 。 一般来说 , 同一个CPU核在同一个时间点 , 只能执行同一个线程 , 但是 , 如果一个核里面有两组寄存器 , 两个pc 。 那么就可以同时执行两组线程 , 在切换线程的时候 , 没必要再去等待寄存器的数据保存和数据载入 。 直接切换到下一组寄存器就可以 。 这就是 超线程。
1.2缓存对齐
一郎科技|Java多线程_缓存对齐CPU到内存之间有很多层的内存 , 如图所示 , CPU需要经过L1 , L2 , L3及主内存才能读到数据 。 从主内存读取数据时的过程如下:
当我左侧的CPU读取x的值的时候 , 首先会去L1缓存中去找x的值 , 如果没有 , 那么取L2 , L3依次去找 。 最后从主内存读入的时候 , 首先将内存数据读入L3 , 然后L2最后L1 , 然后再进行运算 。 但是读取的时候 , 并不是只读一个X的值 , 而是按块去读取(跟电脑的总线宽度有关 , 一次读取一块的数据 , 效率更高) 。 CPU读取X后 , 很可能会用到相邻的数据 , 所以在读X的时候 , 会把同一块中的Y数据也读进来 。 这样在用Y的时候 , 直接从L1中取数据就可以了 。
读取的块就叫做缓存行 , cache line。 缓存行越大 , 局部性空间效率越高 , 但读取时间慢 。 缓存行越小 , 局部性空间效率越低 , 但读取时间快 。 目前多取一个平衡的值 , 64字节 。
然后 , 如果你的X和y在同一块缓存行中 , 且两个字段都用volatile修饰了 , 那么将来两个线程再修改的时候 , 就需要将x和y发生修改的消息高速另外一个线程 , 让它重新加载对应缓存 , 然而另外一个线程并没有使用该缓存行中对应的内容 , 只是因为缓存行读取的时候跟变量相邻 , 这就会产生效率问题 。
【一郎科技|Java多线程_缓存对齐】解决起来也简单 , 我们将数据中的两个volatile之间插入一些无用的内存 , 将第二个值挤出当前缓存行 , 那么执行的时候 , 就不会出现相应问题了 。 提高代码效率 。
2.缓存对齐在java中实现在java中 , jdk一些涉及到多线程的类 , 有时候会看到类似于 public volatile long p1,p2,p3,p4,p5,p6,p7; 这样的代码 , 有的就是做的缓存行对齐 。


推荐阅读