二 、netty的NioEventLoopGroup

        好,我们开始进入netty源码分析的部分。

        NioEventLoopGroup我们从名称中便可得知,它是一组NioEventLoop。那么我们接着看NioEventLoop是什么东西,看它的继承关系

        最底层的继承来自于ScheduledExcutorService,所以,NioEventLoop实际上是一个任务执行器,

        下来我们就一一来看这些实现类主要功能

        AbstractScheduledEventExecutor         

PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;

        首先它维护了一个优先级队列,来进行task的存放,通过pollScheduledTask来从队列中取出task,而PriorityQueue的比较,是由task的deadlineNanos来决定的,实现如下

@Override
public int compareTo(Delayed o) {
    if (this == o) {
        return 0;
    }

    ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
    long d = deadlineNanos() - that.deadlineNanos();
    if (d < 0) {
        return -1;
    } else if (d > 0) {
        return 1;
    } else if (id < that.id) {
        return -1;
    } else if (id == that.id) {
        throw new Error();
    } else {
        return 1;
    }
}

        下来就是一些取消任务,获取任务的操作,比较简单,不再赘述。这里重点要说的是很多文章都回谈到的一个操作,scheduleAtFixedRate和scheduleWithFixedDelay,固定频次执行任务和固定延时执行任务。那么来看一下它的实现,

        两个函数几乎一致,唯一不一样的地方在于

        scheduleAtFixedRate           

return schedule(new ScheduledFutureTask<Void>(
        this, Executors.<Void>callable(command, null),
        ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), unit.toNanos(period)));

        scheduleWithFixedDelay

return schedule(new ScheduledFutureTask<Void>(
        this, Executors.<Void>callable(command, null),
        ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), -unit.toNanos(delay)));

        跟下去看ScheduledFutureTask做了什么样的处理,对于时间的正负,做了如下处理

run(){
    if (p > 0) 
        { deadlineNanos += p; }
         else
        { deadlineNanos = nanoTime() - p; }
}

        nanoTime()是netty中实现,计算当前时间距离class被加载时的绝对时间。对于固定频率,是deadlineNanos加上频次间隔时间,而固定延迟是当前时间间隔加上频次间隔时间,那么二者在正常执行时,其实结果都是一样的。但是如果有以下情况,由于种种原因,线程执行该任务,延迟了n ms,那么固定频次执行的话,那么如果下次正常,会在  (deadlineNanos - n)ms之后执行。而固定延时会在 deadlineNanos 之后执行。两者的区别即,固定频次为固定时间内执行一次,固定延时为执行该次后,等待固定时间,执行下一次。

        接下来的NioEventLoop和SingleThreadEventExecutor结合比较紧密,之间有比较剁的回调,所以放在一起说。

        我们从   SingleThreadEventExecutor 的   public void execute(Runnable task) 说起,对,看了这么久,终于到了执行task的方法了,那么对于进来的任务,它只是简单的放入了Queue中去。然后又执行到doStartTread方法中,之后又回调到NioEventloop的run方法中。NioEventloop便开始处理一些与nio相关的事务了,当

if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
    // - Selected something,
    // - waken up by user, or
    // - the task queue has a pending task.
    // - a scheduled task is ready for processing
    break;
}

        时,它会阻塞一直进行select,当然,它的select 是有超时时间的,为了防止无休止阻塞线程 select(timeoutMillis)。接下来,        

if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
    // timeoutMillis elapsed without anything selected.
    selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
    // The code exists in an extra method to ensure the method is not too big to inline as this
    // branch is not very likely to get hit very frequently.
    selector = selectRebuildSelector(selectCnt);
    selectCnt = 1;
    break;
}

        这段代码几乎所有的文章都回提到,是netty解决jdk臭名昭著bug的代码,那我就再来赘述一遍,select()方法原本是一个阻塞方法,但是由于种种原因,会造成select()没有select到selectionkeys便返回,造成空转最终导致cpu 100%,后来jdk通过cancel掉所有selectionkeys并且调用非阻塞方法selectNow()来修复,然并卵,所以终极方法便是新建一个selector。详情见博客 https://www.cnblogs.com/JAYIT/p/8241634.html ,这也是目前我看到讲的最靠谱的一篇文章。上面一段代码是netty检测空转的方法,即select时间连续一定次数少于给它的超时时间,便认为这个selector废了没救了,那么就只能重建一个了。

// 遍历老的 key
for (SelectionKey key: oldSelector.keys()) {
    Object a = key.attachment();
    try {
        if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
            continue;
        }

        int interestOps = key.interestOps();
        key.cancel();
        // 将 interestOps 重新注册到一个新的 Selector 上 
        SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
        if (a instanceof AbstractNioChannel) {
            // Update SelectionKey
            ((AbstractNioChannel) a).selectionKey = newKey;
        }
        nChannels ++;
    } catch (Exception e) {
        logger.warn("Failed to re-register a Channel to the new Selector.", e);
        if (a instanceof AbstractNioChannel) {
            AbstractNioChannel ch = (AbstractNioChannel) a;
            ch.unsafe().close(ch.unsafe().voidPromise());
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            invokeChannelUnregistered(task, key, e);
        }
    }
}

        接下来它又要回归到一个executor了,通过runAllTasks方法回调回SingleThreadEventExecutor中去,先通过fetchFromScheduledTaskQueue()来抽取所有deadlineNano已经到期的任务,存放在taskQueue中,在runtask方法中依次执行。

        那么最后,回到我们的NioEventLoopGroup,分析完NioEventLoop后,回头发现NioEventLoopGroup实际上十分的简单。首先它继承自MultithreadEventExecutorGroup,MultithreadEventExecutorGroup实现了一些对于多线程的管理工作。

EventExecutor[] children

        用一个数组来存放它管理的线程,这个类中,比较有特色的是

        private final EventExecutorChooser chooser;

        的实现,它的功能很简单就是next()方法返回数组中下个Executor,

        但是却根据队列的长度是否为2的倍数给出两种不同实现 

        判断是否为2的倍数 (val & -val) == val

        return children[childIndex.getAndIncrement() & children.length - 1]; //为2的倍数
        return children[Math.abs(childIndex.getAndIncrement() % children.length)]; //不为2的倍数

        因为位运算的效率是远远高于数学运算的,那么为什么只有2的倍数才能这么玩,主要是当为负数的时候

        2的二进制表示为 10 -2为 11111111111110 所childIndex为负位运算是没有问题的。但是 3的二进制表示为 11,而它的 二进制表示为1111111101,所以进行位运算会得出错误的结果。如果要再进一步说,是因为负数是正数取反+1得到的,所以便会有如此问题。netty为了提高效率,不择手段啊。

        NioEventLoopGroup在SingleThreadEventExecutor的基础上,并无太多的拓展,那么第二篇,NioEventLoopGroup就暂时到此结束吧。

转载于:https://my.oschina.net/vqishiyu/blog/2991942

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值