16.JCTools并发工具

总结
  • JCTools 是专为 JVM 设计的高性能并发工具库,弥补了 JDK 并发队列在极端场景下性能不足的问题
  • 提供四种非阻塞队列:Spsc、Mpsc、Spmc、Mpmc,按生产者/消费者数量选型
  • Netty 内部大量使用 MpscQueue,用于 EventLoop 的任务队列
  • MpscQueue 通过填充 Cache Line 解决 CPU 伪共享问题,避免无效的缓存失效

1. 为什么需要 JCTools?

JDK 自带的非阻塞并发队列(如 ConcurrentLinkedQueue)在高并发场景下性能并不够出色,可选择面也少。

JCTools 是专为 JVM 并发开发设计的开源工具库,提供了一批 JDK 缺失的高性能并发数据结构,包括非阻塞 Map、非阻塞 Queue 等。核心优势是针对特定的生产者/消费者模型做了专项优化,在明确使用场景的前提下,性能远超 JDK 通用实现。

2. 四种队列类型

JCTools 的非阻塞队列按生产者和消费者的数量分为四种:

队列类型 全称 生产者 消费者 典型场景
Spsc Single Producer Single Consumer 1 个 1 个 单线程生产、单线程消费的管道
Mpsc Multi Producer Single Consumer 多个 1 个 多线程提交任务、单线程处理(Netty EventLoop)
Spmc Single Producer Multi Consumer 1 个 多个 单线程分发任务、多线程并行消费
Mpmc Multi Producer Multi Consumer 多个 多个 通用多线程场景

选型原则:能用约束更强的就不用通用的。比如确定只有一个消费者,就用 Mpsc 而不是 Mpmc,约束越强,能做的优化越多,性能越好。

3. Netty 中的 MpscQueue

Netty 的 NioEventLoop 任务队列用的就是 MpscChunkedArrayQueue

这个场景完美匹配 Mpsc 模型,既保证了多线程提交的线程安全,又避免了消费端的锁竞争。

// Netty 中创建 MpscQueue
private final Queue<Runnable> taskQueue = newTaskQueue(maxPendingTasks);

protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
    // 使用 JCTools 的 MpscChunkedArrayQueue
    return maxPendingTasks == Integer.MAX_VALUE
           ? PlatformDependent.<Runnable>newMpscQueue()
           : PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);
}

4. MpscQueue 如何解决 CPU 伪共享?

MpscQueue 在性能优化上做了一个关键处理:填充 Cache Line,解决 CPU 伪共享问题

关于伪共享的原理见 CPU中的伪共享。简单说就是:多个线程访问不同变量,但这些变量恰好在同一个 Cache Line 里,一个线程修改自己的变量会导致其他线程的缓存失效,产生不必要的缓存同步开销。

MpscQueue 的解决方式是在关键变量前后各填充 7 个 long,强制让该变量独占一个 Cache Line(64 字节):

public class FalseSharingPadding {
    protected long p1, p2, p3, p4, p5, p6, p7;  // 前置填充:7 × 8 = 56 字节
    protected volatile long value = 0L;           // 核心变量:8 字节,独占 Cache Line
    protected long p9, p10, p11, p12, p13, p14, p15; // 后置填充:7 × 8 = 56 字节
}

这样不论在什么情况下,value 与其他不相关的变量都处于不同的 Cache Line,多线程并发访问时不会互相干扰。

graph LR
    subgraph Cache Line 1(64 字节)
        A[p1~p7 填充\n56 字节] --- B[value\n8 字节]
    end
    subgraph Cache Line 2(64 字节)
        C[p9~p15 填充\n56 字节] --- D[其他变量]
    end

5. JCTools 队列 vs JDK 队列

对比项 JDK ConcurrentLinkedQueue JCTools MpscQueue
线程模型 通用,任意多生产者多消费者 专用,多生产者单消费者
实现方式 CAS 链表 CAS 数组(减少 GC 压力)
伪共享处理 Cache Line 填充
性能 通用场景够用 高并发下显著更好
适用场景 通用 明确单消费者的场景

JCTools 的代价是使用约束更强,必须严格遵守生产者/消费者数量的约定,否则会出现线程安全问题。用之前要确认好使用场景。