好,我们开始进入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就暂时到此结束吧。