我们在讲解服务端和客户端时经常会看到提交一个任务到channel对应的EventLoop上,后续的io事件监听和任务执行都在EventLoop完成,可以说EventLoop是netty最核心的组件,我们接下来一一分析 剥开这层神秘的面纱

提交一个连接任务异步执行

channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
/***************************SingleThreadEventExecutor************************/
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 判断提交任务的线程是否是EventLoop线程
boolean inEventLoop = inEventLoop();
// 把任务提交到队列中
addTask(task);
if (!inEventLoop) {
// 如果不是 尝试开启EventLoop线程
startThread();
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
// 唤醒正在等待task阻塞的线程,实现在NioEventLoop中,阻塞对象为selector
wakeup(inEventLoop);
}
} 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);
}
}
}
} private void doStartThread() {
assert thread == null;
// 提交一个任务用来开启"EventLoop线程",这里终于用到了executor线程池
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
try {
// !!!终于要干正事了
SingleThreadEventExecutor.this.run();
success = true;
}
}
});
} /***************************NioEventLoop************************/
protected void run() {
// 无限循环
for (;;) {
try {
// selectNoSupplier执行的是selector.selectNow(), hasTasks() 判断任务队列是否有任务
// 如果不为空 那就执行selector.selectNow()返回结果,如果为空返回SelectStrategy.SELECT
// 目的根据是否有任务来决定阻塞
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// 队列中没有任务进行等待io阻塞
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
// 唤醒来自其它线程执行selector.select()的阻塞 因为当前已经获取到最新的io事件了
selector.wakeup();
}
default:
} cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
// 执行io事件
processSelectedKeys();
} finally {
// 执行队列任务
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
// 通过ioRatio和执行io耗费的时长来算出能执行队列任务的时长
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
} private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos); for (;;) {
// 如果有定时任务在定时任务剩余的时间上加0.5ms进行阻塞 默认阻塞1s
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
// 如果恰好在添加task时,wakenUp被设置了true 也就是需要进行唤醒 那么这个任务是不需要现在执行的,如果不这样做,任务可能会被挂起,直到select操作超时。
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
// 不需要唤醒 再执行一次非阻塞的selectNow()随即返回
selector.selectNow();
selectCnt = 1;
break;
} int selectedKeys = selector.select(timeoutMillis);
selectCnt ++; // 如果1s后有返回||select()被唤醒||队列中有任务||有定时任务 则跳出循环
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
} long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// 当前循环时间小于1s且循环次数超过selector重新生成的界限 将当前selector重新生成一个,将旧selector对应的channel也进行重新注册
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
}
}

首先根据队列中是有任务来决定执行带阻塞的selector.select()还是不带阻塞的select.selectNow(),直到

然后执行io事件和队列任务,netty在这里做了一个优化策略,通过ioRatio来控制 执行任务队列的时长,为了防止任务队列一直运行阻塞着影响后续的io事件处理,ioRatio默认为50也就是执行队列任务的时长等于io事件执行的时间

processSelectedKeys()和runAllTasks()方法比较简单,直接追进去一看就明白了了,这里我就不贴了

经过本节分析后,netty整个服务流程就分析的差不多了,涉及到的源码非常多,建议读者多读几遍源码

netty之EventLoop源码分析的更多相关文章

  1. Netty 核心组件 Pipeline 源码分析(二)一个请求的 pipeline 之旅

    目录大纲: 前言 针对 Netty 例子源码做了哪些修改? 看 pipeline 是如何将数据送到自定义 handler 的 看 pipeline 是如何将数据从自定义 handler 送出的 总结 ...

  2. 《深入探索Netty原理及源码分析》文集小结

    <深入探索Netty原理及源码分析>文集小结 https://www.jianshu.com/p/239a196152de

  3. Netty 核心组件 EventLoop 源码解析

    前言 在前文 Netty 启动过程源码分析 (本文超长慎读)(基于4.1.23) 中,我们分析了整个服务器端的启动过程.在那篇文章中,我们重点关注了启动过程,而在启动过程中对核心组件并没有进行详细介绍 ...

  4. 【Netty之旅四】你一定看得懂的Netty客户端启动源码分析!

    前言 前面小飞已经讲解了NIO和Netty服务端启动,这一讲是Client的启动过程. 源码系列的文章依旧还是遵循大白话+画图的风格来讲解,本文Netty源码及以后的文章版本都基于:4.1.22.Fi ...

  5. Netty中FastThreadLocal源码分析

    Netty中使用FastThreadLocal替代JDK中的ThreadLocal[JAVA]ThreadLocal源码分析,其用法和ThreadLocal 一样,只不过从名字FastThreadLo ...

  6. netty(六) buffer 源码分析

    问题 : netty的 ByteBuff 和传统的ByteBuff的区别是什么? HeapByteBuf 和 DirectByteBuf 的区别 ? HeapByteBuf : 使用堆内存,缺点 ,s ...

  7. Netty 5.0源码分析-ByteBuf

    1. 概念 Java NIO API自带的缓冲区类功能相当有限,没有经过优化,使用JDK的ByteBuffer操作更复杂.故而Netty的作者Trustin Lee为了实现高效率的网络传输,重新造轮子 ...

  8. Netty 5.0源码分析-Bootstrap

    1. 前言 io.netty.bootstrap类包提供包含丰富API的帮助类,能够非常方便的实现典型的服务器端和客户端通道初始化功能. 包含的接口类: //提供工厂类的newChannel方法创建一 ...

  9. Netty 5.0源码分析之综述

    1. 前言 本系列主要是用于梳理Netty的架构流程,深入设计细节,重点关注Netty是如何实现它所声称的特性. (ps:本人水平有限,如有错误,请不吝指教 : )) 2. 什么是Netty Nett ...

随机推荐

  1. XPATH基本语法

    1.XPATH与自动化之间的关系 1.XPATH是一门在XML文档中查找信息的语言.XPATH可用来在XML文档中对元素和属性进行遍历. 2.XPATH是用来选择"节点"的一种基于 ...

  2. DVWA学习笔记

    原来装的DVWA没有认认真真地做一遍,靶场环境也有点问题了,到github上面重新下载了一遍:https://github.com/ethicalhack3r/DVWA 复习常见的高危漏洞,产生,利用 ...

  3. 七、TestNG传递参数2

    数据提供者本身可以接受两个类型的参数:Method和ITestContext 在创建@DataProvider方法是可以有四种格式如图: 第一种在上一篇中有学到 1-Methode参数使用 @Data ...

  4. JavaSE09-(练手)简易学生管理系统

    1.学生管理系统实现步骤 案例需求 系统主要功能如下: 添加学生:通过键盘录入学生信息,添加到集合中 删除学生:通过键盘录入要删除学生的学号,将该学生对象从集合中删除 修改学生:通过键盘录入要修改学生 ...

  5. C#跳过工作日,计算几个工作日之后到期的方法

    需求:消费者投诉企业,企业在2个工作日之内做出应答. 分析:1.工作日要刨去周末和法定节假日,而且每年的节假日不一样. 2.消费者可以在任意时间发起投诉,如果在非工作日发起了投诉,那么计算时间应该从工 ...

  6. cloudera集群开启kerberos认证后,删除zk中的/hbase目录

    问题 在cdh集群中开启了kerberos认证,hbase集群出现一点问题,需要通过zookeeper-client访问zookeeper,删除/hbase节点时候报错:Authentication ...

  7. matplotlib的学习1-为什么学他

    1.是一个非常强大的python画图的一个工具 2.手中有很多的数据,但是不知道如何呈现 matplotlib->能画出 线图; 散点图; 等高线图; 条形图; 柱状图; 3D 图形, 甚至是图 ...

  8. wordpress 后台富文本编辑器,添加图片发现无法左对齐,样式出现混乱

    如上图所示,无法左对齐,但是左对齐的按钮全部是正确的,最后一点点排除,发现是因为这个词的影响,去掉就好了,原因不明,可能是这个词被当做某个方法执行了

  9. python爬虫爬取安居客并进行简单数据分析

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 爬取过程一.指定爬取数据二.设置请求头防止反爬三.分析页面并且与网页源码进行比对四.分析页面整理数据 ...

  10. Java基础:String类详解,案例用户登录实现,案例手机号截取实现,案例敏感词替换实现;StringBuilder类详解,StringBuilder和String相互转换,附练习案例.

    1.API 1.1 API概述-帮助文档的使用 什么是API API (Application Programming Interface) :应用程序编程接口 java中的API 指的就是 JDK ...