本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。
背景
在多线程下的生产者-消费者
模型中,需求满足如下情况:
对生产者生产投递数据的性能要求非常高
多个生产者,单个(多个也可以,本文只介绍单个的情况)消费者
当消费者跟不上生产者速度时,可容忍少部分数据丢失
生产者是单条单条地生产数据
举个日志采集
的例子,日志在不同的线程上生产,在日志生产速度远超消费者速度时,可以丢弃部分数据,要求打日志的性能损耗最小,这种情况下可采用本文提供的极致性能的缓冲队列。
实现细节
多个生产者向一个缓冲队列提交消息,说到底是线程安全问题,如果不考虑线程安全,性能必然是最高的,但出现的问题是,数据经常被覆盖。虽然可以容忍少部分数据丢失,但也是在消费者跟不上生产者速度时。缓冲区必然有界,无界可能导致内存泄露,如果缓冲区满,再生产新数据,可选的策略一般有如下几种:
- 阻塞直到被消费
- 覆盖旧数据
- 丢弃新数据
在要求对生产者性能损耗最小的情况下一般不选1,通常采取覆盖策略。
环形队列
有一种环形队列的数据结构(ring buffer
)可以很好的解决解决上面提到的生产者-消费者模型、缓冲区有界、覆盖策略。通常用数组来实现ring buffer,只要保证生产者获取下标是线程安全的即可解决线程安全问题。而且数组内存预先分配加上连续内存索引更加快速的特点也保证了强悍的性能。
AtomicInteger
在环形队列上如何保证线程安全地获取数组下标?线程安全地自增我们想到了AtomicInteger
,很容易写出如下代码
public class AtomicRangeInteger extends Number {
private final AtomicInteger value;
private final int startValue;
private final int endValue;
public AtomicRangeInteger(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
this.value = new AtomicInteger(startValue);
}
public final int incrementAndGet() {
int next;
do {
next = value.incrementAndGet();
if (next > endValue && value.compareAndSet(next