入口:在SingleThreadEventExecutor的构建函数中taskQueue = newTaskQueue(this.maxPendingTasks);详情看eventLoop的构建
先看队列的重要属性
//生产者的最大下标
private volatile long producerLimit;
//生产者数组的mask,偏移量offset & mask得到在数组的位置
protected long producerMask;
//生产者的buffer,由于会产生的新的数组buffer,所以生产者和消费者各自维护自己的buffer
protected E[] producerBuffer;
//生产者的当前下标
private volatile long producerIndex;
//消费者的当前下标
private volatile long consumerIndex;
//消费者数组的mask
protected long consumerMask;
//生产者的buffer
protected E[] consumerBuffer;
看一下构造方法,这里的容量、坐标都是2的N次,这是为了做位运算
public MpscChunkedArrayQueue(int initialCapacity, int maxCapacity, boolean fixedChunkSize) {
if (initialCapacity < 2) {
throw new IllegalArgumentException("Initial capacity must be 2 or more");
} else if (maxCapacity < 4) {
throw new IllegalArgumentException("Max capacity must be 4 or more");
} else if (Pow2.roundToPowerOfTwo(initialCapacity) >= Pow2.roundToPowerOfTwo(maxCapacity)) {
throw new IllegalArgumentException("Initial capacity cannot exceed maximum capacity(both rounded up to a power of 2)");
} else {
int p2capacity = Pow2.roundToPowerOfTwo(initialCapacity);
// 容量是虚扩成2倍,所以后面看到下标index追加时是加2,而不是1
// 而且在获取数组的偏移量offset时,把数组的arrayIndexScale也除以了2
// 这样做是为了方便位计算
long mask = (long)(p2capacity - 1 << 1);
E[] buffer = CircularArrayOffsetCalculator.allocate(p2capacity + 1);
this.producerBuffer = buffer;
this.producerMask = mask;
this.consumerBuffer = buffer;
this.consumerMask = mask;
this.maxQueueCapacity = (long)Pow2.roundToPowerOfTwo(maxCapacity) << 1;
this.isFixedChunkSize = fixedChunkSize;
this.soProducerLimit(mask);
}
}
看一下offer函数:利用pIndex来判断容量,然后利用cas来更改pIndex,然后设置值
public boolean offer(E e) {
if (null == e) {
throw new NullPointerException();
} else {
while(true) {
while(true) {
long offset = this.lvProducerLimit();
long pIndex = this.lvProducerIndex();
// 如果pIndex最后一位是1,就代表其他线程正在扩容。因为扩容速度很快
// ,不涉及数据迁移等操作,所以此线程只会再这里自旋,不会做其他操作。
if ((pIndex & 1L) != 1L) {
long mask = this.producerMask;
E[] buffer = this.producerBuffer;
if (offset <= pIndex) {
int result = this.offerSlowPath(mask, buffer, pIndex, offset);
// 0、default成功了
// 1 继续重试
// 2 容量满了
// 3 需要扩容
switch(result) {
case 0:
default:
break;
case 1:
continue;
case 2:
return false;
case 3:
this.resize(mask, buffer, pIndex, this.consumerIndex, this.maxQueueCapacity, e);
return true;
}
}
// 容量没问题 进行cas操作更改pIndex
if (this.casProducerIndex(pIndex, pIndex + 2L)) {
offset = modifiedCalcElementOffset(pIndex, mask);
UnsafeRefArrayAccess.soElement(buffer, offset, e);
return true;
}
}
}
}
}
}
我们看一下offerSlowPath方法
private int offerSlowPath(long mask, E[] buffer, long pIndex, long producerLimit) {
long consumerIndex = this.lvConsumerIndex();
long maxQueueCapacity = this.maxQueueCapacity;
long bufferCapacity = this.getCurrentBufferCapacity(mask, maxQueueCapacity);
int result = 0;
// 说明此块buffer已经消费了部分,可以不扩容
if (consumerIndex + bufferCapacity > pIndex) {
if (!this.casProducerLimit(producerLimit, consumerIndex + bufferCapacity)) {
// cas失败 重试
result = 1;
}
// 容量满了
} else if (consumerIndex == pIndex - maxQueueCapacity) {
result = 2;
// 需要扩容
} else if (this.casProducerIndex(pIndex, pIndex + 1L)) {
result = 3;
} else {
// 重试
result = 1;
}
return result;
}
扩容方法:
private void resize(long mask, E[] buffer, long pIndex, long consumerIndex, long maxQueueCapacity, E e) {
int newBufferLength = this.getNextBufferCapacity(buffer, maxQueueCapacity);
E[] newBuffer = CircularArrayOffsetCalculator.allocate(newBufferLength);
this.producerBuffer = newBuffer;
this.producerMask = (long)(newBufferLength - 2 << 1);
long offsetInOld = modifiedCalcElementOffset(pIndex, mask);
long offsetInNew = modifiedCalcElementOffset(pIndex, this.producerMask);
// 在新buffer插入对象
UnsafeRefArrayAccess.soElement(newBuffer, offsetInNew, e);
// 在老buffer设置新buffer的地址
UnsafeRefArrayAccess.soElement(buffer, this.nextArrayOffset(mask), newBuffer);
long available = maxQueueCapacity - (pIndex - consumerIndex);
if (available <= 0L) {
throw new IllegalStateException();
} else {
// min是为了保证容量不超
this.soProducerLimit(pIndex + Math.min(mask, available));
// 再原来buffer的offset上插入jump,让消费者知道该换buffer了
// 新buffer的地址在老buffer的最后一个元素
UnsafeRefArrayAccess.soElement(buffer, offsetInOld, JUMP);
this.soProducerIndex(pIndex + 2L);
}
}
负责新构建一个buffer,然后新老连接起来,在老buffer最后一个元素设置新buffer的地址。在老buffer的元素(插入的元素)位置设置Jump,新buffer的元素位置(新老一样)设置元素。演示一下,假设数组容量为5,那么最多可以装三个元素,需要留一个给JUMP,留一个给下一个Buffer的地址。
+------+------+------+------+------+
| | | | | |
| E1 | E2 | E3 | JUMP | NBP | Buffer1
| | | | | |
+------+------+------+------+------+
+------+------+------+------+------+
| | | | | |
| E1 | E2 | JUMP | E3 | NBP | Buffer2
| | | | | |
+------+------+----------+------+------+
+------+------+------+-----+------+
| | | | | |
| E1 | JUMP | E3 | E3 | NBP | Buffer3
| | | | | |
+------+---------+------+-----+------+
看一下poll方法:Mpsc中poll方法是线程不安全的,所以代码比较简单,没有cas操作。
public E poll() {
E[] buffer = this.consumerBuffer;
long index = this.consumerIndex;
long mask = this.consumerMask;
long offset = modifiedCalcElementOffset(index, mask);
Object e = UnsafeRefArrayAccess.lvElement(buffer, offset);
if (e == null) { // 拿到的元素是null
// 已经消费完了
if (index == this.lvProducerIndex()) {
return null;
}
// 因为生产者是先改pIndex,再赋值,所以可能需要自旋等待生产者赋值
do {
e = UnsafeRefArrayAccess.lvElement(buffer, offset);
} while(e == null);
}
// 如果是JUMP就跳到下一个buffer消费
if (e == JUMP) {
E[] nextBuffer = this.getNextBuffer(buffer, mask);
return this.newBufferPoll(nextBuffer, index);
} else {
// 改变cIndex
UnsafeRefArrayAccess.soElement(buffer, offset, (Object)null);
this.soConsumerIndex(index + 2L);
return e;
}
}
总结一下:
- 大量的位运算,速度更改
- 使用Unsafe.putOrderedXXX(putXxxVolatile会让其他线程立刻看到值,性能差些)
- 无锁化
- 伪共享(本文没展现去掉了填充代码)
了解一下unsafe的方法
- putOrderedXxx(),使用 StoreStore 屏障,会把最新值更新到主内存,但不会立即失效其它缓存行中的数据,是一种延时更新机制;
- putXxxVolatile(),使用 StoreLoad 屏障,会把最新值更新到主内存,同时会把其它缓存行的数据失效,或者说会刷新其它缓存行的数据;
- putXxx(obj, offset),不使用任何屏障,和普通变量一样;
- getXxxVolatile(),使用 LoadLoad 屏障,会从主内存获取最新值;
- getXxx(obj, offset),不使用任何屏障,和普通变量一样;