先看看EventLoop类图

我们在Netty第二篇文章中的代码中,看到有多次用到eventLoop.execute()方法,这个方法就是EventLoop开启线程执行任务的关键,跟踪进去看看

// 类SingleThreadEventExecutor
SingleThreadEventExecutor#execute(Runnable task)
-->
SingleThreadEventExecutor#execute0(@Schedule Runnable task)
-->
private void execute(Runnable task, boolean immediate) {
// 判断当前线程是否为eventLoop的线程
boolean inEventLoop = inEventLoop();
// 将任务添加进taskQueue
addTask(task);
if (!inEventLoop) {
// 开启eventLoop的线程
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
// The task queue does not support removal so the best thing we can do is to just move on and
// hope we will be able to pick-up the task before its completely terminated.
// In worst case we will log on termination.
}
if (reject) {
reject();
}
}
} if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}

这段代码,我们分3部分解读

添加任务

将任务添加进队列,等待调用

对应代码

// addTask(task);
// 类SingleThreadEventExecutor
protected void addTask(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
// 添加任务
if (!offerTask(task)) {
// 如果添加失败,执行拒绝执行处理器
reject(task);
}
}

SingleThreadEventExecutor有一个类变量来存储task

private final Queue<Runnable> taskQueue;

开启线程

开启线程是以下这段逻辑

// 判断当前线程是否eventLoop的线程
// 如果不是,则开启EventLoop的线程
if (!inEventLoop) {
// 开启eventLoop的线程
startThread();
。。。。。。
}
-->
// 类SingleThreadEventExecutor
private void startThread() {
// 一些状态判断,保证doStartThread只会被执行一次
if (state == ST_NOT_STARTED) {
// cas修改状态
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
// 实际开启线程的方法
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
-->
// 类SingleThreadEventExecutor
private void doStartThread() {
assert thread == null;
// 这个Executor在EventLoopGroup构造时,就已经注入
// MultithreadEventExecutorGroup
// executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
} boolean success = false;
updateLastExecutionTime();
try {
// 这个整个NioEventLoop的核心,里面是个死循环
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
。。。。。。
}
}
});
}

在前面EventLoopGroup的创建与初始化一节有说到,executor的实例化

executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
executor.execute(new Runnable() {...});
// 不深究细节的话,上面两行代码效果类比下面两行
Thread thread = new Thread(new Runnable() {...});
thread.start();

所以,executor.execute这里,就开了一个新的线程。

而这个线程,主要处理的是SingleThreadEventExecutor.**this**.run();,也就是NioEventLoop#run()

那这个run方法是干嘛的,看下图红框部分

因为run方法比较重要,此处不做代码省略,看注释

protected void run() {
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
// 如果没有任务,则返回SELECT
// 有任务,则获取io事件的个数。此时如果strategy >= 0
// 如果有io任务,优先io任务,然后才执行普通任务
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue; case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO case SelectStrategy.SELECT:
// 下一次定时任务触发截止时间
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
// 再次判断没有任务
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
} finally {
// This update is just to help block unnecessary selector wakeups
// so use of lazySet is ok (no race condition)
// 阻止不必要的唤醒
nextWakeupNanos.lazySet(AWAKE);
}
// fall through
default:
}
} catch (IOException e) {
// If we receive an IOException here its because the Selector is messed up. Let's rebuild
// the selector and retry. https://github.com/netty/netty/issues/8566
rebuildSelector0();
selectCnt = 0;
handleLoopException(e);
continue;
} selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
// 控制处理io事件的事件占用比例,默认是百分之50,一半时间用来处理io事件,一半时间用来处理任务
final int ioRatio = this.ioRatio;
boolean ranTasks;
// 100%表示执行完全部任务,才进入下一轮循环
if (ioRatio == 100) {
try {
if (strategy > 0) {
// 在对应的 Channel 上处理 IO 事件
processSelectedKeys();
}
} finally {
// Ensure we always run tasks.
// 执行queueTask中全部的任务
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
// 在对应的 Channel 上处理 IO 事件
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
// 0表示运行运行最小数量的任务,即63个
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
} if (ranTasks || strategy > 0) {
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
selectCnt = 0;
} else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
selectCnt = 0;
}
} catch (CancelledKeyException e) {
// Harmless exception - log anyway
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
} catch (Error e) {
throw e;
} catch (Throwable t) {
handleLoopException(t);
} finally {
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Error e) {
throw e;
} catch (Throwable t) {
handleLoopException(t);
}
}
}
}

run里面是个死循环,关键方法有selectprocessSelectedKeysrunAllTasks

  • select

    select方法很简单,就是选择合适的阻塞时间,等待IO事件触发

    private int select(long deadlineNanos) throws IOException {
    if (deadlineNanos == NONE) {
    return selector.select();
    }
    // Timeout will only be 0 if deadline is within 5 microsecs
    // 如果deadlineNanos小于5纳秒,则为0,,否则取整为1毫秒
    // 这段操作是为了向上取整,转成毫秒
    long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
    // 如果timeoutMillis大于0,就阻塞selector同样的时间
    // 这段是为了获取最近的延时任务
    return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
    }
  • runAllTasks

    此方法会取出可执行的任务,并执行。

    上一节添加任务讲的addTask,放入的任务就是在这里被执行的。

  • processSelectedKeys

    此方法在有IO事件时才触发,这个下面细讲

小结一下,run的死循环中主要判断有无IO事件,有则处理,处理完IO事件,再处理队列中的任务。

如果没有IO事件,也没有待处理的任务,则阻塞等待。

唤醒线程

对应代码

// wakeup(inEventLoop);
// 类NioEventLoop
protected void wakeup(boolean inEventLoop) {
// 不是当前EventLoop在执行的时候,才需要唤醒
// nextWakeupNanos放入AWAKE是阻止不必要的唤醒
if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
selector.wakeup();
}
}

阻塞是selecto.select,那么唤醒就是selector.wakeup

Netty源码解读(三)-NioEventLoop的更多相关文章

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

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

  2. 【Netty源码解析】NioEventLoop

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

  3. go语言 nsq源码解读三 nsqlookupd源码nsqlookupd.go

    从本节开始,将逐步阅读nsq各模块的代码. 读一份代码,我的思路一般是: 1.了解用法,知道了怎么使用,对理解代码有宏观上有很大帮助. 2.了解各大模块的功能特点,同时再想想,如果让自己来实现这些模块 ...

  4. Netty源码解读(二)-服务端源码讲解

    简单Echo案例 注释版代码地址:netty 代码是netty的源码,我添加了自己理解的中文注释. 了解了Netty的线程模型和组件之后,我们先看看如何写一个简单的Echo案例,后续的源码讲解都基于此 ...

  5. Netty源码分析之NioEventLoop(二)—NioEventLoop的启动

    上篇文章中我们对Netty中NioEventLoop创建流程与源码进行了跟踪分析.本篇文章中我们接着分析NioEventLoop的启动流程: Netty中会在服务端启动和新连接接入时通过chooser ...

  6. Netty源码解读(四)-读写数据

    读写Channel(READ)的创建和注册 在NioEventLoop#run中提到,当有IO事件时,会调用processSelectedKeys方法来处理. 当客户端连接服务端,会触发服务端的ACC ...

  7. Netty源码解读(一)-前置准备

    前置条件 源码版本netty4.1 了解Java NIO.Reactor模型和Netty的基本使用. 解释一下: Java NIO:了解BIO和NIO的区别以及Java NIO基础API的使用 Rea ...

  8. jQuery源码解读三选择器

    直接上jQuery源码截取代码 // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ i ...

  9. Python Web Flask源码解读(三)——模板渲染过程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

随机推荐

  1. 如何在Linux上恢复误删除的文件或目录

    Linux不像windows有那么显眼的回收站,不是简单的还原就可以了.linux删除文件还原可以分为两种情况,一种是删除以后在进程存在删除信息,一种是删除以后进程都找不到,只有借助于工具还原,这里分 ...

  2. 推荐一款数据mock框架,无需任何依赖,贼牛逼

    fox-mock 是基于Java Agent实现的自测,联调Mock利器.能解决你的这些问题: 开发过程中,依赖了下游多个接口,想跑个单测都必须得等下游把服务部署好 联调过程中,下游某个接口出问题,阻 ...

  3. MySQL存储过程入门了解

    0.环境说明: mysql版本:5.7 1.使用说明 ​ 存储过程是数据库的一个重要的对象,可以封装SQL语句集,可以用来完成一些较复杂的业务逻辑,并且可以入参出参(类似于java中的方法的书写). ...

  4. Centos7最小化安装报错There are no enabled repos. Run "yum repolist all" to see the repos you have.解决办法

    原因是缺少CentOS-Base.repo文件,因为我这台机器wget也不能用,所以我是下载到本地sftp上去的,传输的时候一定要在root用户下,否则会无法启动传输 这是报错的完整信息:Loadin ...

  5. 零基础学Java第二节(运算符、输入、选择流程控制)

    本篇文章是<零基础学Java>专栏的第二篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 第一章 运算符 1.1 算术运算符的概述和用法 运算符 对常量和变 ...

  6. Nvidia Triton使用教程:从青铜到王者

    1 相关预备知识 模型:包含了大量参数的一个网络(参数+结构),体积10MB-10GB不等 模型格式:相同的模型可以有不同的存储格式(可类比音视频文件),目前主流有torch.tf.onnx和trt, ...

  7. linux篇-centos7搭建apache服务器(亲测可用)

    1安装apache yum install httpd httpd-devel -y 2开启服务器 systemctl start httpd.service 3开机自启 systemctl enab ...

  8. 103_Power Pivot 透视表中空白标签处理及百分比

    焦棚子的文章目录 请点击下载附件 1.案例来源于不断变化的需求 事实表:销售表 维度表:城市表 销售表和城市建立多对一的关系 如图1: 图1 2.插入透视表 如图2: 图2 3.问题 1.销售表中,城 ...

  9. ajax与python后端交互

    目录 ajax简介 前后端传输数据编码格式 ajax发送json格式数据 ajax携带文件数据 回调机制处理策略 ajax简介 ajax可以在页面不刷新的情况下可以与后端进行数据交互,异步提交,局部刷 ...

  10. [刷题] IDA*

    BZOJ3041 水叮当的舞步 Description & Solution 见hzw的博客 http://hzwer.com/1507.html Code // Author: wlzhou ...