文章目录
NioEventLoop实例化过程
-
本文集合代码流程来分析 NioEventLoop 的实例化过程
-
开篇我们牢记 NioEventLoop 的两个功能:
1.处理IO事件;执行与 Channel 相关的 IO 操作, 包括调用 select 等待就绪的 IO 事件、读写数据与数
据的处理等;
2.执行提交的任务;作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提
交的定时任务也是这个线程执行的;
一、构造方法
- 实例化自然离不开构造方法,不管是我们直接构造对象还是框架内部去构造对象都会走构造方法,因为NioEventLoop 是一个最底层的实现类,继承链有非常多的父类,我们先通过这些父类梳理一下构造方法执行的顺序;
1.1 NioEventLoop
- 首先是:NioEventLoop,它只有一个构造方法如下:
/**
* 构造方法
*/
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
//1.调用 SingleThreadEventLoop 的构造方法
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
//参数校验
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
//2.SelectorProvider 用于创建 Selector
provider = selectorProvider;
//3.创建 Selector 对象,注意内部 通过 SelectorTuple 封装了Selector对象
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
}
1.2 SingleThreadEventLoop
- 然后是super调用 SingleThreadEventLoop 的构造方法,因为 SingleThreadEventLoop 比较简单,因此构造方法只是帮助初始化了一个 tailTasks 任务队列属性,super会调用 SingleThreadEventExecutor 的构造方法
private final Queue<Runnable> tailTasks;
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
tailTasks = newTaskQueue(maxPendingTasks);
}
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
return new LinkedBlockingQueue<Runnable>(maxPendingTasks);
}
1.3 SingleThreadEventExecutor
- SingleThreadEventExecutor:SingleThreadEventExecutor的属性比较多,同时会调用父类初始化NioEventLoop 所属的 NioEventLoopGroup
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
//1.初始化所属的 EventLoopGroup
super(parent);
//2.是否只在 addTask 的时候唤醒执行线程
this.addTaskWakesUp = addTaskWakesUp;
//3.拒绝任务之前的待定线程数量
this.maxPendingTasks = Math.max(16, maxPendingTasks);
//4.执行器
this.executor = ObjectUtil.checkNotNull(executor, "executor");
//5.待定任务队列
taskQueue = newTaskQueue(this.maxPendingTasks);
//6.饱和策略
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
1.4 AbstractEventExecutor
- AbstractEventExecutor:调用 AbstractEventExecutor 初始化 parent 属性,指定EventLoop所属的 EventLoopGroup
@Override
public EventExecutorGroup parent() {
return parent;
}
- 到此初始化的过程结束了,下面是两张图来表示初始化的过程:
- 上面两个图都展示了初始化过程中构造方法的调用过程
二、EventLoop 与 Channel 的关联
- EventLoop 可以管理很多个Channel,并不断轮询这些Channel 所发生的的事件,充当了NIO中select 所在的线程的角色。EventLoop 与 Channel 的关联其实就是 Channel注册到NioEventLoop的过程,我们来看看这个过程。
2.1 Channel注册到EventLoop
- 在 io.netty.channel.AbstractChannel 的内部类 AbstractUnsafe 中,有 register 注册方法,该方法是将Channel 注册到 EventLoop
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//1.EventLoop 参数校验
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
//2.校验未注册,已经注册了就返回
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
//3.校验 Channel 和 EventLoop 匹配兼容,isCompatible方法需要子类实现,
//因为不同类型的 Channel 需要和不同类型的,EventLoop 匹配,子类通常是判断 EventLoop的
// 类型(二者都是接口,有众多实现)
if (!isCompatible(eventLoop)) {
promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
//4.设置Channel的eventLoop属性,将二者关联起来,其实这就是注册,注册在代码层面的表示很简单,就是在Channel内部有一个成员变量
//来保存 EventLoop 对象,这就表示该Channel 所注册的 EventLoop,
AbstractChannel.this.eventLoop = eventLoop;
//5.在 EventLoop线程 中执行注册逻辑,如果当前线程就在 EventLoop 中,就直接注册,否则就以任务的形式提交给 EventLoop 执
//行注册,不要忘记 EventLoop 本质是一个线程池,可以接受任务
if (eventLoop.inEventLoop()) {
//6.直接注册
register0(promise);
} else {
//7.以人物的形式提交给 EventLoop 注册
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + ": register");
register0(promise);
}
});
} catch (Throwable t) {
//异常处理
logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
- 这里需要注意的有两点:一个是注册的代码表示其实简单,一个EventLoop 可以管理N个Channel,Channel内部有一个 EventLoop 的成员属性,它注册到某一个EventLoop,就将这个EventLoop保存到内部的这个属性即可,非常简单;
- 另一个是注册的任务需要交给对应的 EventLoop 内部的线程执行,因为程序启动的时候这个线程很可能并不是EventLoop内部的线程,如果是就执行,如果不是就将注册的动作以任务的形式提交给EventLoop ,EventLoop自己具备执行任务的功能,后续执行注册即可
2.2 EventLoopGroup注册Channel
- 注册时机在16-EventLoopGroup整体 的3.1小结已有分析,这里还是把那部分内容搬过来
EventLoopGroup 的一个重要功能就是将 Channel 注册到 EventLoop,注册动作实际上是由内部的EventLoop完成的。EventLoopGroup会
通过 next 方法选择一个内部的 EventLoop 来执行注册动作(后面会介绍选择策略),最底层本质是在EventLoop的实现子类的内部维护
一个集合,集合内部保存注册在自己身上的 Channel 对象。这里可以简单跟一下源码,可能有点绕。
- 首先我们看到 EventLoopGroup 的注册功能是在子类 MultithreadEventLoopGroup 实现的,代码如下:
//MultithreadEventLoopGroup 内部选择一个EventLoop来注册
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
@Override
public EventLoop next() {
return (EventLoop) super.next();
}
- 在 register 方法打上断点启动后调用栈如下:
- 大致的调用流程是:
AbstractBootstrap#bind() ->
-> AbstractBootstrap#doBind()
-> AbstractBootstrap#initAndRegister()
-> MultithreadEventLoopGroup#register
-> EventLoopGroup#register(io.netty.channel.Channel)
-> SingleThreadEventLoop#register(io.netty.channel.ChannelPromise)
- 1.首先是启动器的 bind 方法,进入 bind 逻辑,调用 AbstractBootstrap 的 bind 和 doBind 方法
- 2.然后进入 initAndRegister 方法完成注册逻辑,内部会通过 config 配置对象获取到 EventLoopGroup 对象,调用 EventLoopGroup 的 register 方法
- 3.往后继续走 MultithreadEventLoopGroup 的register 方法,通过next 获取一个内部的 EventLoop 对象,执行 register,然后就走到了 EventLoop 的register 方法
- 4.走到 EventLoop 的实现类 SingleThreadEventLoop 的 register 方法
- 5.继而走到与 SingleThreadEventLoop 所关联的 AbstractChannel 的 register 方法
- 6.最后注册逻辑走到 AbstractNioChannel 的 register 方法,(核心代码如下):我们看看到注册的代码是不是和NIO几乎一样,形如:channel.register(selector) 返回selectKey,有兴趣可以查阅参考文章[3]的2.2 示例代码
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (; ; ) {
try {
//这段代码,是不是和NIO代码很相似
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
// TODO TODO 1003 doRegister 异常
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
- 集合2.1 和 2.2,就把整个 Channel 注册到 NioEventLoop 的流程梳理了,2.1是NioEventLoop的实现部分,2.2是NioEventLoopGroup的实现部分,NioEventLoopGroup本质还是调用内部的 NioEventLoop来完成Channel 到 NioEventLoop的注册
- 整个过程:
启动器Bootstrap#bind -> EventLoopGroup#register -> EventLoop#register -> Channel内部类AbstractUnsafe#register方法注册 -> 改变内部的属性 -> 注册完成
- 简易流程图如下:
三、EventLoop启动
- 通过前面的两个步骤,我们知道了 EventLoop 的构造基本流程和Channel注册到 EventLoop 的过程,但是 EventLoop 作为一个处理 Channel 事件的线程,什么时候启动呢?
- 之前我们有说过 NioEventLoop 内部持有一个线程,NioEventLoop的启动就是这个线程的启动,这个线程对象保存在 SingleThreadEventExecutor,他的启动在 SingleThreadEventExecutor#startThread 方法
3.1 SingleThreadEventExecutor#startThread
//io.netty.util.concurrent.SingleThreadEventExecutor#startThread
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
- doStartThread 执行逻辑主体
/**
* 启动 EventExecutor 内部的线程(如果是EventLoop实现类,启动的就是EventLoop内部的线程)
*/
private void doStartThread() {
assert thread == null;
//1.使用启动器执行一个任务
executor.execute(new Runnable() {
@Override
public void run() {
//2.新启动的线程就是当前 EventExecutor 内部的线程
thread = Thread.currentThread();
//3.如果当前线程已经被标记打断,则进行打断操作。
if (interrupted) {
thread.interrupt();
}
//4.是否执行成功的标记
boolean success = false;
//5.更新最后执行时间
updateLastExecutionTime();
try {
//6.执行任务,这里会启动EventLoop,注意这里走的是run,而不是start方法
//这里仅仅只是一个普通的run启动而已,因为这里已经是在 EventExecutor 对应的
//线程里面执行了,这里面对应的 NioEventLoop实现就是死循环监听Channel 的事件
SingleThreadEventExecutor.this.run();
//7.标记执行成功
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
//8.优雅关闭
for (; ; ) {
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
if (logger.isErrorEnabled()) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
"be called before run() implementation terminates.");
}
}
//关闭前确认,执行完剩余任务
try {
// Run all remaining tasks and shutdown hooks.
for (; ; ) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
// 清理,释放资源
cleanup();
} finally {
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
if (logger.isWarnEnabled()) {
logger.warn("An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
3.2 NioEventLoop#run
- 我们看到 doStartThread 里面会启动 EventLoop 内部的线程,并且会做一系列的线程状态设置,以及关闭后的资源清理工作,而 NioEventLoop 子类则将需要做的在run 方法中重写,如下:
/**
* NioEventLoop 的主体循环逻辑,一个死循环,不断的处理 Channel事件,类似于NIO中的while(true){ ... } 死循环
*/
@Override
protected void run() {
//1.死循环,处理注册在自己身上的Channel 的事件
for (; ; ) {
try {
//2.有任务就执行selectNow,没有任务就返回-1
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
//3.默认实现下,不存在这个情况。
case SelectStrategy.CONTINUE:
continue;
//4.SELECT值为-1,返回-1表示hasTasks为false,没有任务
case SelectStrategy.SELECT:
// 重置 wakenUp 标记为 false
// 选择( 查询 )任务
select(wakenUp.getAndSet(false));
// 'wakenUp.compareAndSet(false, true)' is always evaluated
// before calling 'selector.wakeup()' to reduce the wake-up
// overhead. (Selector.wakeup() is an expensive operation.)
//
// However, there is a race condition in this approach.
// The race condition is triggered when 'wakenUp' is set to
// true too early.
//
// 'wakenUp' is set to true too early if:
// 1) Selector is waken up between 'wakenUp.set(false)' and
// 'selector.select(...)'. (BAD)
// 2) Selector is waken up between 'selector.select(...)' and
// 'if (wakenUp.get()) { ... }'. (OK)
//
// In the first case, 'wakenUp' is set to true and the
// following 'selector.select(...)' will wake up immediately.
// Until 'wakenUp' is set to false again in the next round,
// 'wakenUp.compareAndSet(false, true)' will fail, and therefore
// any attempt to wake up the Selector will fail, too, causing
// the following 'selector.select(...)' call to block
// unnecessarily.
//
// To fix this problem, we wake up the selector again if wakenUp
// is true immediately after selector.select(...).
// It is inefficient in that it wakes up the selector for both
// the first case (BAD - wake-up required) and the second case
// (OK - no wake-up required).
// 唤醒。原因,见上面中文注释
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
//ioRatio表示期望的线程处理IO事件的时间比例
final int ioRatio = this.ioRatio;
//既然期望是100%,那么久一直处理IO任务
if (ioRatio == 100) {
try {
//处理 Channel 感兴趣的就绪 IO 事件
processSelectedKeys();
} finally {
// 运行所有普通任务和定时任务,不限制时间
// Ensure we always run tasks.
runAllTasks();
}
//如果不是100%,那么就指定 runAllTasks 的运行时间
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// 运行所有普通任务和定时任务,限制时间
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
//优雅关闭,如果抛出来异常,就会处理 shutdown
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
- 对比NIO的代码 (NIO代码可以参考 03-BIO、NIO到Netty ),我们看到这里很相似,在一个死循环中通过selectStrategy去监听事件,但是我们只看到一点影子,我们姑且认为 switch 逻辑在获取事件,方法后面部分则是根据 ioRatio 配置来处理IO事件,这里 switch 里面的 select 内部还有很多逻辑先不展开,下一篇文章:《Netty 的 IO事件处理》再开展这部分的内容;
四、小结
- 本文主要梳理了 NioEventLoop 的实例化过程,其中包括构造方法执行流程,Channel 注册到 NioEventLoop 的流程,以及 NioEventLoop 启动后的任务执行过程,但是并未深入了解任务执行过程中对事件和任务的处理细节,后文针对 NioEventLoop 的IO事件处理开展。
- 下一篇文章:《NioEventLoop IO事件处理》继续;
来源:CSDN
作者:学圆惑边
链接:https://blog.csdn.net/my_momo_csdn/article/details/103569520