这是我参与 8 月更文挑战的第 14天,活动详情查看: 8月更文挑战
上一篇我们已经说了尽可能扩充队列长度,多线程情况下是否一定要加锁呢,答案是否定的,并给了一定的解释了,现在看下Disruptor怎么做的;
Disruptor设计的是确保任何数据都应该只由一个线程拥有进行写访问的权限,从而消除了写争用,可以理解为java.util.concurrent.Phaser,因为它具有相似元素,功能上类似的 CyclicBarrier和CountDownLatch,但支持更灵活的使用,引入以支持 Fork-Join;目的是最大限度地提高内存分配的效率,并以缓存友好的方式运行,以便在现代硬件上以最佳方式运行。
Disruptor核心是一个预先分配的环形缓冲区形式的有界数据结构。数据通过一个或多个生产者添加到环形缓冲区,并由一个或多个消费者处理。环形缓冲区所需内存在启动时预先分配,由于该环形缓冲区在Disruptor的持续时间内有效,所以在内存利用较高的项目中,垃圾回收器不需要进行回收,造成的负担也就相应变小了,所以我们必须减少垃圾回收的动作,
有界队列中的头部尾部在获取的时候都会有竞争现象,由于环形的特殊结构,便不存在这种现象,我们只需关注入队和出队;
排序是 Disruptor 管理并发的核心概念。每个生产者和消费者都根据严格的顺序概念来确定它如何与环形缓冲区交互。生产者在环中按顺序声明下一个插槽。在多个生产者的情况下使用 CAS 操作更新的原子计数器。一旦声明了一个序列值,环形缓冲区中的这个数据现在就可以被声明的生产者写入。当生产者完成对数据的更新后,它可以通过更新一个单独的计数器来提交更改,该计数器代表消费者可用的最新数据的环形缓冲区上的光标,而光标对消费者可见是通过内存屏障来实现,环形缓冲区如果出队时落后了一些步骤,那么它会在不涉及并发的情况下快速处理到与入队相同的步伐从而稳定系统;
Disruptor 说这是此类数据交换的最高性能机制。通过专注于跨线程数据交换中所涉及问题的清晰分离,通过消除写入争用,最小化读取争用并确保代码与现代处理器使用的缓存良好配合,创建了一种在任何应用程序中的线程之间用于交换数据的高效机制。
允许消费者在没有任何争用的情况下处理达到给定阈值的数据的批处理效应;对于大多数系统,随着负载和争用的增加,延迟呈指数增长,即特征“J”曲线。随着 Disruptor 上负载的增加,延迟几乎保持不变,直到内存子系统发生饱和。