原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注

今天是猿灯塔“365篇原创计划”第七篇。
接下来的时间灯塔君持续更新Netty系列一共九篇
Netty 源码解析(一): 开始
Netty 源码解析(二): Netty 的 Channel
Netty 源码解析(三): Netty 的 Future 和 Promise
Netty 源码解析(四): Netty 的 ChannelPipeline
Netty 源码解析(五): Netty 的线程池分析
Netty 源码解析(六): Channel 的 register 操作
当前:Netty 源码解析(七): NioEventLoop 工作流程
Netty 源码解析(八): 回到 Channel 的 register 操作
Netty 源码解析(九): connect 过程和 bind 过程分析
今天呢!灯塔君跟大家讲: 
NioEventLoop 工作流

NioEventLoop 工作流程

前面,我们在分析线程池的实例化的时候说过,NioEventLoop 中并没有启动 Java 线程。这里我们来仔细分析下在 register 过程中调用的 eventLoop.execute(runnable) 这个方法,这个代码在父类 SingleThreadEventExecutor 中:
 @Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 判断添加任务的线程是否就是当前 EventLoop 中的线程
boolean inEventLoop = inEventLoop();
// 添加任务到之前介绍的 taskQueue 中,
// 如果 taskQueue 满了(默认大小 16),根据我们之前说的,默认的策略是抛出异常
addTask(task);
if (!inEventLoop) {
// 如果不是 NioEventLoop 内部线程提交的 task,那么判断下线程是否已经启动,没有的话,就启动线程
startThread();
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
原来启动 NioEventLoop 中的线程的方法在这里。另外,上节我们说的 register 操作进到了 taskQueue 中,所以它其实是被归类到了非 IO 操作的范畴。
下面是 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() 方法:
  private void doStartThread() {
assert thread == null;
// 这里的 executor 大家是不是有点熟悉的感觉,它就是一开始我们实例化 NioEventLoop 的时候传进来的 ThreadPerTaskExecutor 的实例。它是每次来一个任务,创建一个线程的那种 executor。
// 一旦我们调用它的 execute 方法,它就会创建一个新的线程,所以这里终于会创建 Thread 实例
executor.execute(new Runnable() {
@Override
public void run() {
// 看这里,将 “executor” 中创建的这个线程设置为 NioEventLoop 的线程!!!
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 执行 SingleThreadEventExecutor 的 run() 方法,它在 NioEventLoop 中实现了
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// ... 我们直接忽略掉这里的代码
}
}
});
}
上面线程启动以后,会执行 NioEventLoop 中的 run() 方法,这是一个非常重要的方法,这个方法肯定是没那么容易结束的,必然是像 JDK 线程池的 Worker 那样,不断地循环获取新的任务的。它需要不断地做 select 操作和轮询 taskQueue 这个队列。我们先来简单地看一下它的源码,这里先不做深入地介绍:
 @Override
protected void run() {
// 代码嵌套在 for 循环中
for (;;) {
try {
// selectStrategy 终于要派上用场了
// 它有两个值,一个是 CONTINUE 一个是 SELECT
// 针对这块代码,我们分析一下。
// 1. 如果 taskQueue 不为空,也就是 hasTasks() 返回 true,
// 那么执行一次 selectNow(),该方法不会阻塞
// 2. 如果 hasTasks() 返回 false,那么执行 SelectStrategy.SELECT 分支,
// 进行 select(...),这块是带阻塞的
// 这个很好理解,就是按照是否有任务在排队来决定是否可以进行阻塞
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// 如果 !hasTasks(),那么进到这个 select 分支,这里 select 带阻塞的
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
// 默认地,ioRatio 的值是 50
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
// 如果 ioRatio 设置为 100,那么先执行 IO 操作,然后在 finally 块中执行 taskQueue 中的任务
try {
// 1. 执行 IO 操作。因为前面 select 以后,可能有些 channel 是需要处理的。
processSelectedKeys();
} finally {
// 2. 执行非 IO 任务,也就是 taskQueue 中的任务
runAllTasks();
}
} else {
// 如果 ioRatio 不是 100,那么根据 IO 操作耗时,限制非 IO 操作耗时
final long ioStartTime = System.nanoTime();
try {
// 执行 IO 操作
processSelectedKeys();
} finally {
// 根据 IO 操作消耗的时间,计算执行非 IO 操作(runAllTasks)可以用多少时间.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}上面这段代码是 NioEventLoop 的核心,这里介绍两点:
  1. 首先,会根据 hasTasks() 的结果来决定是执行 selectNow() 还是 select(oldWakenUp),这个应该好理解。如果有任务正在等待,那么应该使用无阻塞的 selectNow(),如果没有任务在等待,那么就可以使用带阻塞的 select 操作。
  2. ioRatio 控制 IO 操作所占的时间比重:
  • 如果设置为 100%,那么先执行 IO 操作,然后再执行任务队列中的任务。
  • 如果不是 100%,那么先执行 IO 操作,然后执行 taskQueue 中的任务,但是需要控制执行任务的总时间。也就是说,非 IO 操作可以占用的时间,通过 ioRatio 以及这次 IO 操作耗时计算得出。
我们这里先不要去关心 select(oldWakenUp)、processSelectedKeys() 方法和 runAllTasks(…) 方法的细节,只要先理解它们分别做什么事情就可以了。回过神来,我们前面在 register 的时候提交了 register 任务给 NioEventLoop,这是 NioEventLoop 接收到的第一个任务,所以这里会实例化 Thread 并且启动,然后进入到 NioEventLoop 中的 run 方法。
当然了,实际情况可能是,Channel 实例被 register 到一个已经启动线程的 NioEventLoop 实例中。

365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板

Netty 源码解析(七): NioEventLoop 工作流程的更多相关文章

  1. 【Netty源码解析】NioEventLoop

    上一篇博客[Netty源码学习]EventLoopGroup中我们介绍了EventLoopGroup,实际说来EventLoopGroup是EventLoop的一个集合,EventLoop是一个单线程 ...

  2. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  3. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  4. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  5. Netty 源码解析(六): Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇   Netty 源码解析(一 ):开始 Netty ...

  6. Netty 源码解析(五): Netty 的线程池分析

    今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  7. Netty 源码解析(四): Netty 的 ChannelPipeline

    今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  8. Netty 源码解析(二):Netty 的 Channel

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...

  9. Netty源码分析之NioEventLoop(三)—NioEventLoop的执行

    前面两篇文章Netty源码分析之NioEventLoop(一)—NioEventLoop的创建与Netty源码分析之NioEventLoop(二)—NioEventLoop的启动中我们对NioEven ...

随机推荐

  1. Linux 文件系统常用命令

    文件系统查看命令df df:查看分区,单位默认是KB df -h 统计目录或文件大小du du /etc/:会列出/etc/目录下的所有子目录所占的空间,最后给出/etc/目录的大小,属于高负载命令, ...

  2. 记一次discuz修改首页图片路径问题

    1.找到图片路径拼装文件 首先打开根目录下的template目录找到首页文件 打开后找到图片列表的拼装位置 // 链接示例: <!--{eval $imagelistkey = getforum ...

  3. Jmeter之Json提取器详解(史上最全)

    参考资料:https://www.bbsmax.com/A/D854lmBw5E/ Jsonpath在线测试:http://jsonpath.com/ 实际工作中用到的一些场景: 提取某个特定的值 提 ...

  4. SpringSecurity(2)---记住我功能实现

    SpringSecurity(2)---记住我功能实现 上一篇博客实现了认证+授权的基本功能,这里在这个基础上,添加一个 记住我的功能. 上一篇博客地址:SpringSecurity(1)---认证+ ...

  5. 逐点分析,这样做Web端性能测试

    前言: 71%用户希望在手机上打开网页能跟电脑一样快: 5秒钟被认为是用户能忍受的最长响应时间,如果响应时间超过5秒,50%的移动用户会放弃: 33%失望的用户会使用竞品替代: 用户尝试三次出现同样性 ...

  6. kafka能做什么?kafka集群配置 (卡夫卡 大数据)

    什么是Kafka 官网介绍: 几个概念: 详细介绍 : 操作kafka: kafka集群 消息测试 问题检测 什么是Kafka 官网介绍: ApacheKafka是一个分布式流媒体平台.这到底是什么意 ...

  7. 面试了 6 轮 Google 中国 之后,还是挂了

    去年换工作的时候, 面试了一下 Google (这里说的是 Google 中国哈), 来了个 Google 面试六轮游, 结果是没通过.

  8. Vue —— VueX精讲(1)

    大纲 这一讲我们最主要的就是学习vue中的数据管理VueX,这个是一个大杀器 一.回顾一些Promise相关的东西 Promise 有几个比较重要的方法,最重要的还是有一个叫做all的方法,这个也是非 ...

  9. getline使用问题

    1.输入string string s1; getline(cin,s1); cin>>s1; //注意cin遇到空格会终止,而getline不会 2.关于吞回车问题 输入n后要记得吞回车 ...

  10. CGAL代码阅读跳坑指南

    CGAL代码阅读跳坑指南 整体框架介绍 CGAL中的算法和数据结构由它们使用的对象类型和操作参数化.它们可以处理满足特定语法和语义需求的任何具体模板参数.为了避免长参数列表,参数类型被收集到一个单独的 ...