Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

# Disruptor 系列二

发表于 2022-02-27 分类于 Java 阅读次数： 阅读次数： Disqus：

这里开始慢慢深入的讲一下 disruptor，首先是 `lock free` , 相比于前面介绍的两个阻塞队列，
disruptor 本身是不直接使用锁的，因为本身的设计是单个线程去生产，通过 cas 来维护头指针，
不直接维护尾指针，这样就减少了锁的使用，提升了性能；第二个是这次介绍的重点，
减少 `false sharing` 的情况，也就是常说的 伪共享 问题，那么什么叫 伪共享 呢，
这里要扯到一些 cpu 缓存的知识，
譬如我在用的这个笔记本
这里就可能看到 L2 Cache 就是针对每个核的
这里可以看到现代 CPU 的结构里，分为三级缓存，越靠近 cpu 的速度越快，存储容量越小，
而 L1 跟 L2 是 CPU 核专属的每个核都有自己的 L1 和 L2 的，其中 L1 还分为数据和指令，
像我上面的图中显示的 L1 Cache 只有 64KB 大小，其中数据 32KB，指令 32KB，
而 L2 则有 256KB，L3 有 4MB，其中的 Line Size 是我们这里比较重要的一个值，
CPU 其实会就近地从 Cache 中读取数据，碰到 `Cache Miss` 就再往下一级 Cache 读取，
每次读取是按照缓存行 `Cache Line` 读取，并且也遵循了“就近原则”，
也就是相近的数据有可能也会马上被读取，所以以行的形式读取，然而这也造成了 `false sharing`，
因为类似于 `ArrayBlockingQueue`，需要有 `takeIndex` , `putIndex` , `count` , 因为在同一个类中，
很有可能存在于同一个 `Cache Line` 中，但是这几个值会被不同的线程修改，
导致从 Cache 取出来以后立马就会被失效，所谓的就近原则也就没用了，
因为需要反复地标记 dirty 脏位，然后把 Cache 刷掉，就造成了`false sharing`这种情况
而在 `disruptor` 中则使用了填充的方式，让我的头指针能够不产生`false sharing`

    class LhsPadding
    {
        protected long p1, p2, p3, p4, p5, p6, p7;
    }

    class Value extends LhsPadding
    {
        protected volatile long value;
    }

    class RhsPadding extends Value
    {
        protected long p9, p10, p11, p12, p13, p14, p15;
    }

    /**
     * <p>Concurrent sequence class used for tracking the progress of
     * the ring buffer and event processors.  Support a number
     * of concurrent operations including CAS and order writes.
     *
     * <p>Also attempts to be more efficient with regards to false
     * sharing by adding padding around the volatile field.
     */
    public class Sequence extends RhsPadding
    {

通过代码可以看到，sequence 中其实真正有意义的是 value 字段，因为需要在多线程环境下可见也
使用了`volatile` 关键字，而 `LhsPadding` 和 `RhsPadding` 分别在value 前后填充了各
7 个 `long` 型的变量，`long` 型的变量在 Java 中是占用 8 bytes，这样就相当于不管怎么样，
value 都会单独使用一个缓存行，使得其不会产生 `false sharing` 的问题。