40 | 案例分析(三):高性能队列Disruptor
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
Disruptor是一款高性能的有界内存队列,相比于Java SDK提供的ArrayBlockingQueue和LinkedBlockingQueue,Disruptor在高并发场景下表现更出色。其性能优势主要体现在合理的内存分配、避免伪共享、无锁算法和支持批量消费等方面。Disruptor使用RingBuffer数据结构,通过一次性全部创建数组元素和循环利用对象,提升了缓存命中率并避免了频繁的GC。此外,Disruptor还充分考虑了程序的局部性原理,通过连续的内存地址和循环利用Event来提升性能。总的来说,Disruptor在高性能队列方面具有明显的优势,被广泛应用于Log4j2、Spring Messaging、HBase、Storm等项目中。 Disruptor在优化并发性能方面可谓是做到了极致,优化的思路大体是两个方面,一个是利用无锁算法避免锁的争用,另外一个则是将硬件(CPU)的性能发挥到极致。尤其是后者,在Java领域基本上属于经典之作了。 Disruptor提供了经典的实现,通过填充方式避免伪共享,这对于Java程序员来说是一个重要的优化技巧。文章还提到了Java 8中提供了避免伪共享的注解@sun.misc.Contended,但需要注意避免伪共享是以牺牲内存为代价的,具体使用时需要仔细斟酌。文章内容涉及了伪共享、CPU缓存失效、Disruptor中的无锁算法等技术细节,对于对性能优化感兴趣的读者来说,是一篇值得深入阅读的文章。
《Java 并发编程实战》,新⼈⾸单¥59
全部留言(48)
- 最新
- 精选
- Juc希望老师解释下,为什么创建元素的时间离散会导致元素的内存地址不是连续的?这些元素不是存在数组中的吗?数组初始化不是已经连续分配内存了吗?
作者回复: 数组连续,数组里只有引用,e1 e2这些对象的地址不连续
2019-05-30256 - 冰激凌的眼泪前后56个字节保证了目标字段总是独占一个cache line,不受周围变量缓存失效的影响
作者回复: 👍
2019-07-03523 - 万历十五年单机提升性能不外乎是围绕CPU,内存和IO想办法。 CPU: 1.避免线程切换:单线程,对于多线程进行线程绑定,使用CAS无锁技术 2.利用CPU缓存,还有缓存填充,设计数据结构和算法 内存: 1.多级缓存:应用缓存,第三方缓存,系统缓存 2.数组优于链表 3.避免频繁内存碎片:利用池思想复用对象 解决IO产生的速度差: 1.多路复用 2.队列削峰 3.协程
作者回复: ������������
2020-12-2022 - 惘 闻if (wrapPoint>cachedGatingSequence || cachedGatingSequence>current)这个位置的判断原本不明白,看了几遍总算是明白了. 环形队列,生产者从0生产 消费者从0消费. wrapPoint是指生产了一圈 又达到了 消费者消费的最小的位置 如果此时继续生产,那么消费最少的消费者还未消费的消息将会被生产者覆盖,所以此处要停止. 而 最小消费位置大于生产者当前生产位置的话,说明消费到了生产者还未生产消息的位置,所以等待消息的生产,要停止.
作者回复: 👍🏻
2020-08-26217 - xinglichea老师,感觉填充的模式不是很靠谱,程序的健壮性要强依赖于CPU的缓存行的实现,打个比如,如果以后CPU缓存行变成了128个字节,那企不要写Disruptor的实现源码,然后原来实现的代码仍然会有伪共享的问题!!!
作者回复: 我觉得从官方提供注解这一行为来看,应该不至于不靠谱,不过java程序员都习惯于不关注硬件。
2019-08-295 - 那月真美老师,数组内存地址连续,数组里面的引用对象怎么做到连续呢?它不是由生产者产生的吗?何时生产只能由生产者决定,初始化数组的时候怎么一次性初始化数组元素啊?
作者回复: Disruptor 内部的 RingBuffer 也是用数组实现的,但是这个数组中的所有元素在初始化时是一次性全部创建的,所以这些元素的内存地址大概率是连续的。一次性全部创建,大概率事关键词。参考文中LongEvent的例子,生产用的是set方法,而不是add方法
2020-09-163 - 张洋1.if (wrapPoint>cachedGatingSequence || cachedGatingSequence>current) 这点开始一直没看懂,之前写过环形队列,每次索引都会重置,就是一直在0-9之间,然后看了下RingBuffer 好像它的索引是一直累加的。这样就好懂多了。 2.关于ArrayBlockQueue 添加的对象是不连续的还是不太明白,数组初始化 不是在内存种开辟出一段连续的内存空间吗? 还是按照有的同学留言所说的,如果是引用对象不一定是连续的。
作者回复: 数组里所有的对象的引用一定是连续的,但是对象不一定连续
2020-12-292 - 神佑小鹿ArrayBlockingQueue 的入队和出队操作是用锁来保证互斥的,所以入队和出队不会同时发生。如果允许入队和出队同时发生,那就会导致线程 A 和线程 B 争用同一个缓存行,这样也会导致性能问题。 想问下加锁了就没有缓存干扰了吗!为啥?
作者回复: 缓存导致的可见性问题靠的是HB规则,锁是靠HB规则解决缓存问题的
2019-11-2622 - 码农Kevin亮老师,避免伪共享的逻辑有点困惑: 伪共享逻辑上就是没实现共享,而disruptor用行填充也是没实现共享。那么为什么避免伪共享就能提升性能呢?
作者回复: 共享,指的是多个核能共享缓存,避免伪共享后,多个核是可以共享缓存的
2019-06-021 - 全麦小面包老师,有个问题哈。Disruptor创建的event不是业务数据类,里面set的东西才是业务需要的。但set对象的创建还是离散的,难道set对象的引用,能和event一起缓存??java有这种机制吗?
作者回复: 没有这种机制
2022-07-26归属地:北京