Netty 源码 NioEventLoop(一)初始化与启动

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

相关文章:

Netty 基于事件驱动模型,使用不同的事件来通知我们状态的改变或者操作状态的改变。它定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象。

  • Channel:Netty 网络操作抽象类,EventLoop 主要是为 Channel 处理 I/O 操作,两者配合参与 I/O 操作。

  • EventLoopGroup:一个 EventLoop 的分组,它可以获取到一个或者多个 EventLoop 对象,因此它提供了迭代出 EventLoop 对象的方法。

1. EventLoop 类图

其中 Executor、ExecutorService、AbstractExecutorService、ScheduledExecutorService 属于 JDK 定义的规范,Netty 实现了自己的自定义线程池。

  • EventExecutorGroup 提供了 next() 方法,除此之外是线程池的生命周期方法,如 shutdownGracefully。
  • EventLoopGroup 提供了 register 方法,将 channel 绑定到线程上。
  • EventExecutor 继承自 EventExecutorGroup,提供了 inEventLoop 方法。
  • EventLoop 继承自 EventLoopGroup,提供了 register 注册和 parent 方法。
  • MultithreadEventExecutorGroup 基于多线程的 EventExecutor (事件执⾏行器)的分组抽象类
  • ThreadPerTaskExecutor 实现 Executor 接⼝口,每个任务一个线程的执行器实现类

2. NioEventLoopGroup 初始化

(1) NioEventLoopGroup

NioEventLoopGroup 构造方法中最重要的一件事是创建子线程 NioEventLoop,创建完成后子线程并未启动,该线程在 channel 注册时启动。

public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}
  • nThreads, threadFactory 前两个参数用于创建线程池,如果 nThreads=0 则默认为 CPU 核数的 2 倍
  • chooserFactory 用于循环获取 NioEventLoopGroup 中的下一个 NioEventLoop 的算法。2 的幂次方使用位运算
  • selectorProvider, selectStrategyFactory 后二个参数用于创建 selector
  • rejectedExecutionHandler 异常处理的 Handler

(2) MultithreadEventExecutorGroup

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 1. executor 用于创建一个子线程
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
} // 2. 创建所有的子线程 children
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
// 有异常则需要销毁资源
}
}
} // 3. chooser 用于循环获取 children 中的下一个元素的算法。2 的幂次方使用位运算
chooser = chooserFactory.newChooser(children); // 4. children 初始化完成则设置 setSuccess 为 true
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
} // 5. 返回一个只读的 children 暴露给开发者
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}

MultithreadEventExecutorGroup 创建了所有的子线程,其中最重要的方法是 newChild(executor, args)

(3) newChild

// NioEventLoopGroup 创建子线程 NioEventLoop
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}

(4) NioEventLoop

NioEventLoop 构造时就创建了一个 selector 对象。下面看一个 NioEventLoop 的创建过程。

// 创建了一个 selector 对象
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
} // 负责 channel 的注册
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
tailTasks = newTaskQueue(maxPendingTasks);
} // 负责执行任务
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ObjectUtil.checkNotNull(executor, "executor");
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

通过以上步骤就创建了 NioEventLoop 对象,但这个线程并未启动。很显然在 channel 注册到 NioEventLoop 时会启动该线程。

3. NioEventLoop 启动过程

在 Channel 注册到 eventLoop 上时会执行 execute() 方法,启动线程。

// NioServerSocketChannel -> AbstractChannel.AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 同一个 channel 的注册、读、写等都在 eventLoop 完成,避免多线程的锁竞争
if (eventLoop.inEventLoop()) {
// 将 channel 注册到 eventLoop 上
register0(promise);
} else {
// 若 eventLoop 线程没有启动,启动该线程
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
// 省略...
}
}
}

(1) execute

在 eventLoop 执行 execute 方法时,如果线程还未启动则需要先启动线程。

// SingleThreadEventExecutor
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} // 是否在 EventLoop 线程中
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
// 启动线程
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
} // 唤醒线程
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}

!addTaskWakesUp 表示“添加任务时,是否唤醒线程”?!但是,怎么使⽤用取反了。这样反倒变成了,“添加任务时,是否【不】唤醒线程”。具体的原因是为什么呢?

真正的意思是,“添加任务后,任务是否会自动导致线程唤醒”。为什么呢?

  • 对于 Nio 使用的 NioEventLoop ,它的线程执行任务是基于 Selector 监听感兴趣的事件,所以当任务添加到 taskQueue 队列中时,线程是无感知的,所以需要调用 #wakeup(boolean inEventLoop) 方法,进行主动的唤醒。

  • 对于 Oio 使用的 ThreadPerChannelEventLoop,它的线程执行是基于 taskQueue 队列列监听(阻塞拉取)事件和任务,所以当任务添加到 taskQueue 队列中时,线程是可感知的,相当于说,进行被动的唤醒。

(2) 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);
}
}
}
} // 真正启动线程,执行的是 NioEventLoop 中的 run 任务
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
} boolean success = false;
updateLastExecutionTime();
try {
// run 方法由子类 NioEventLoop 实现,是一个死循环
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// 如果执行到这里,则说明需要关闭该线程
}
}
});
}

executor 默认是在 MultithreadEventExecutorGroup 的构造方法完成初始化的,代码如下:

executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());

public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory; public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
} @Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}

ThreadPerTaskExecutor 通过线程工厂 threadFactory 创建一个线程并启动,至此 NioEventLoop 开始工作了。在这个简单的类中使用的代理模式和命令模式两种设计模式。

(3) 线程状态变化

SingleThreadEventExecutor 维护了线程的状态字段 state。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Netty 源码 NioEventLoop(一)初始化的更多相关文章

  1. Netty 源码 NioEventLoop(三)执行流程

    Netty 源码 NioEventLoop(三)执行流程 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 上文提到在启动 N ...

  2. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  3. Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器

    Netty源码分析第二章:NioEventLoop   第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...

  4. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  5. Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器

    Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...

  6. Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建

    Netty源码分析第二章: NioEventLoop   第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...

  7. Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动

    Netty源码分析第二章: NioEventLoop   第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...

  8. Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector

    Netty源码分析第二章: NioEventLoop   第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...

  9. Netty源码分析第2章(NioEventLoop)---->第6节: 执行select操作

    Netty源码分析第二章: NioEventLoop   第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到select操作的入口 ...

随机推荐

  1. 温故而知新-正则单词和strlen

    1 正则表达式用\b表示单词的开始和结束 \bblog\b 正则查找blog这个单词 2 关于strlen的汉字问题 在utf8格式下  strlen('汉字')=6 在gbk格式下 strlen(' ...

  2. 使MySQL查询区分大小写的实现方法

    发布:mdxy-dxy 字体:[增加 减小] 类型:转载 我们在MySQL中使用SELECT语句查询时,可不可以使查询区分大小写?今天从网络上找到了方法,现总结如下.   1.一种方法是可以设置表或行 ...

  3. Node NPM 的常用配置

    1,修改 npm 下载模块的 保存地址 <1>  进入 cmd 运行, 如下命令 npm config set prefix  "C:\Program File\NodeJs\p ...

  4. leetcode235

    /** * Definition for a binary tree node. * public class TreeNode { * public int val; * public TreeNo ...

  5. jmeter随机函数

    有些接口的字段,入参须唯一. 高并发压测的时候,这个比较棘手,可以用多个随机函数组合 如:两个__RandomString中间,夹个__Random ${__RandomString(2,qwerty ...

  6. 在linux中运行main方法所在的java类(亲测有效!!!)

    本人是用SecureCRTPortable连接linux终端的.其他工具连接linux终端应该是一样的操作! 一.首先到移动到java工程所在的bin目录. 二.在bin目录下执行javac -cp ...

  7. postgresql 的操作

    基本操作: \o /tmp/11.txt ,查询结果输出到文件 \d 查询table结构 \x 切换显示方式 postgresql中可以导出某个sql的执行结果到文件中,方法是在psql中首先执行\o ...

  8. ArcGIS模型构建器案例学习笔记-字段处理模型集

    ArcGIS模型构建器案例学习笔记-字段处理模型集 联系方式:谢老师,135-4855-4328,xiexiaokui@qq.com 由四个子模型组成 子模型1:判断字段是否存在 方法:python工 ...

  9. 给定一个十进制数,将其转化为N进制数-----17年滴滴笔试题

    题目:给定一个十进制数M,将其转化为N进制数,其中2<=N<=16,其中N为32为整型数; 输入:M N,如7 2 输出转化结果:111 注意点:考虑负数的情况,记得添加负号(其实直接添加 ...

  10. Bean Validation技术实现对Javabean的校验

    概述:在java开发时,由于分层的原因(表现层-控制层-业务层-数据持久层),有时候需要对传入的Javabean进行校验,如果过多的校验会导致比较繁琐,做重复的工作,下面将介绍Bean Validat ...