Netty-无锁队列-MpscChunkedArrayQueue

入口:在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的方法

  1. putOrderedXxx(),使用 StoreStore 屏障,会把最新值更新到主内存,但不会立即失效其它缓存行中的数据,是一种延时更新机制;
  2. putXxxVolatile(),使用 StoreLoad 屏障,会把最新值更新到主内存,同时会把其它缓存行的数据失效,或者说会刷新其它缓存行的数据;
  3. putXxx(obj, offset),不使用任何屏障,和普通变量一样;
  4. getXxxVolatile(),使用 LoadLoad 屏障,会从主内存获取最新值;
  5. getXxx(obj, offset),不使用任何屏障,和普通变量一样;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值