再谈ArrayBlockingQueue/LinkedBlockingQueue和Disruptor

1 ArrayBlockingQueue

 

 

循环数组

一把锁

3个属性伪共享

 

2 LinkedBlockingQueue

无法避免在队列中只有一个元素时的线程安全问题。

为解决上述问题,LinkedBlockingQueue的head节点始终指向一个空节点(在构造时,会将head指向一个空节点),则即为设定了当集合中只有一个元素(空节点)时认定集合为空时,不再出队

 

3 为什么array~不用两把锁

3.1 b.It may be that Doug Lea didn't feel that Java needed to support 2 different BlockingQueues that differed only in their allocation scheme.

https://stackoverflow.com/questions/50739951/what-is-the-reason-to-implement-arrayblockingqueue-with-one-lock-on-tail-and-hea

3.2

LinkedBlockingQueue添加元素时有一个构造节点的时间,为了尽量减少这部分时间占比,使用一个读锁一个写锁可以实现并发存取的优化。而ArrayBlockingQueue在添加元素时不需要开辟空间等等(创建时指定数组大小),所以反而是加锁解锁时间占比较大,如果继续使用这种读写锁分离的设计,增加的时间损耗大于并发读写带来的收益。

3.3

数组的入队和出队时间复杂度低,不像列表需要额外维护节点对象。所以当入队和出队并发执行时,阻塞时间很短。如果使用双锁的话,会带来额外的设计复杂性,如count应被volatile修饰,并且赋值需要CAS操作等。而且ArrayBlockingQueue是定长的,当putIndex==length时,putIndex会重置为0,这样入队和出队的index可能是同一个,在这种情况下还需要考虑锁之间的通讯,参考读写锁

应该不会同一个,会阻塞

 

4 disruptor

4.1 数组,CPU缓存友好

4.2 生产消费比链表性能好

4.3 ring buffer大小2的n次方,适配位运算

4.4 cas

4.5 伪共享

 

5 Disruptor 3.2.1版本源码

5.1 RingBuffer/MultiProducerSequencer

5.1.1 

long[] paddedValue = new long [15]

5.1.2 offset放在第8个long,两边追加7个long(56字节)防伪共享

static {
int base = UNSAFE.arrayBaseOffset(long[].class);
int scale = UNSAFE.arrayIndexScale(long[].class);
VALUE_OFFSET = (long)(base + scale * 7);
}

5.1.3 前值

public long get() {
return UNSAFE.getLongVolatile(this.paddedValue, VALUE_OFFSET);
}

5.1.4 cas取得可写入位置,更改成功意味着这个槽当前线程拿下了

public boolean compareAndSet(long expectedValue, long newValue) {
return UNSAFE.compareAndSwapLong(this.paddedValue, VALUE_OFFSET, expectedValue, newValue);
}

5.2 com.lmax.disruptor.WorkProcessor#run

5.2.1 新消费者Sequence

com.lmax.disruptor.WorkerPool#WorkerPool(com.lmax.disruptor.RingBuffer<T>, com.lmax.disruptor.SequenceBarrier, com.lmax.disruptor.ExceptionHandler, com.lmax.disruptor.WorkHandler...)

5.2.2 cas消费

do {
nextSequence = this.workSequence.get() + 1L;
this.sequence.set(nextSequence - 1L);
} while(!this.workSequence.compareAndSet(nextSequence - 1L, nextSequence));

 

5.3 取模

private int calculateIndex(long sequence) {
return (int)sequence & this.indexMask;
}
///
this.indexMask = bufferSize - 1;

 

 

内存屏障

StoreStore:每个Volatile写操作前加上StoreStore屏障,禁止上面的普通写和它重排;
StoreLoad:每个Volatile写操作后面加StoreLoad屏障,禁止跟下面的Volatile读和它重排;
LoadLoad:每个Volatile读操作前面加LoadLoad屏障,禁止跟上面的普通读和Volatile读重排;
LoadStore:每个Volatile读操作后面加LoadStore屏障,禁止禁止下面的普通写和Volatile读重排;

 

 

 

posted on 2025-06-21 23:57  silyvin  阅读(36)  评论(0)    收藏  举报