欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1. EventLoop 介绍
继承图:
说明:
-
ScheduledExecutorService 接口表示是一个定时任务接口,EventLoop 可以接受定时任务。
-
EventLoop 接口:Netty 接口文档说明该接口作用:一旦 Channel 注册了,就处理该 Channel 对应的所有I/O 操作。
-
SingleThreadEventExecutor 表示这是一个单个线程的线程池
-
EventLoop 是一个单例的线程池,里面含有一个死循环的线程不断的做着 3 件事情:监听端口,处理端口事件,处理队列事件。每个 EventLoop 都可以绑定多个 Channel,而每个 Channel 始终只能由一个 EventLoop 来处理
2. NioEventLoop 用的使用 - execute 方法
2.1 源码剖析
在 EventLoop 的 使 用 , 一 般 就 是 eventloop.execute(task)。看 下 execute 方 法 的 实 现 ( 在SingleThreadEventExecutor 类中)
@Override
public void execute(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
private void execute(Runnable task, boolean immediate) {
/**
* 判断Thread.currentThread()当前线程是不是与NioEventLoop绑定的本地线程;
* 如果Thread.currentThread()== this.thread, 那么只用将execute()方法中的task添加到任务队列中就好;
* 如果Thread.currentThread()== this.thread 返回false, 那就先调用startThread()方法启动本地线程,然后再将task添加到任务队列中.
*/
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread(); // 启动线程
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
...
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
说明:
-
首先判断该 EventLoop 的线程是否是当前线程,然后直接添加到任务队列中
- 如果不是,则尝试启动线程(但由于线程是单个的,因此只能启动一次)。
-
如果线程已经停止,并且删除任务失败,则执行拒绝策略,默认是抛出异常。
-
如果 addTaskWakesUp 是 false,并且任务不是 NonWakeupRunnable 类型的,就尝试唤醒 selector。这个时候,阻塞在 selecor 的线程就会立即返回
2.1.1 addTask 和 offerTask 方法源码
//addTask
protected void addTask(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
if (!offerTask(task)) {
reject(task);
}
}
//offerTask
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
2.1.2 NioEventLoop 类的父类 SingleThreadEventExecutor的 startThread 方法
当执行 execute 方法的时候,如果当前线程不是 EventLoop 所属线程,则尝试启动线程,也就是 startThread 方法,dubug 代码如下
private void startThread() {
if (state == ST_NOT_STARTED) {
//设置thread 的状态 this.state是 ST_STARTED 已启动
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
//真正启动线程的函数, 其作用类似于 thread.start()
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
说明:
- 该方法首先判断是否启动过了,保证 EventLoop 只有一个线程,如果没有启动过,则尝试使用 Cas 将 state 状态改为 ST_STARTED,也就是已启动。然后调用 doStartThread 方法。如果失败,则进行回滚。
private void doStartThread() {
//启动线程之前,必须保证thread 是null,其实也就是thread还没有启动。
assert thread == null;
/** 通过executor启动一个新的task, 在task里面启动this.thread线程。*/
executor.execute(new Runnable() {
@Override
public void run() {
// 1. 将当前thread 赋值给 this.thread 也就是将启动的线程赋值给本地绑定线程thread
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 2. 实际上是调用NioEventLoop.run() 方法实现 事件循环机制
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
...
} finally {
for (;;) { // 死循环
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
...
try {
for (;;) {
if (confirmShutdown()) {
break;
}
}
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTDOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTDOWN)) {
break;
}
}
confirmShutdown();
} finally {
//确保事件循环结束之后,关闭线程,清理资源。
try {
cleanup();
} finally {
FastThreadLocal.removeAll();
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.countDown();
int numUserTasks = drainTasks();
if (numUserTasks > 0 && logger.isWarnEnabled()) {
logger.warn("An event executor terminated with " +
"non-empty task queue (" + numUserTasks + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
说明:
-
首先调用 executor 的 execute 方法,这个 executor 就是在创建 Event LoopGroup 的时候创建的ThreadPerTaskExecutor 类。该 execute 方法会将 Runnable 包装成 Netty 的 FastThreadLocalThread。
-
任务中,首先判断线程中断状态,然后设置最后一次的执行时间。
-
执行当前 NioEventLoop 的 run 方法,注意:这个方法是个死循环,是整个 EventLoop 的核心
-
在 finally 块中,使用 CAS 不断修改 state 状态,改成 ST_SHUTTING_DOWN。也就是当线程 Loop 结束的时候。关闭线程。最后还要死循环确认是否关闭,否则不会 break。然后,执行 cleanup 操作,更新状态为ST_TERMINATED,并释放当前线程锁。如果任务队列不是空,则打印队列中还有多少个未完成的任务。并回调 terminationFuture 方法。
-
其实 最核心的就是 Event Loop 自身的 run 方法。再继续深入 run 方法
2.2 EventLoop 的中的 Loop 是靠 run 实现的, run 方法(在该方法在 NioEventLoop)
protected void run() {
int selectCnt = 0;
//所有的逻辑操作都在for循环体内进行,只有当NioEventLoop接收到退出指令的时候,才退出循环,否则一直执行下去,这也是通用的NIO线程实现方式。
/** 死循环:NioEventLoop 事件循环的核心就是这里! */
for (;;) {
try {
int strategy;
try {
// 1.通过 select/selectNow 调用查询当前是否有就绪的 IO 事件
// 当 selectStrategy.calculateStrategy() 返回的是 CONTINUE, 就结束此轮循环,进入下一轮循环;
// 当返回的是 SELECT, 就表示任务队列为空,就调用select(Boolean);
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;
}
//2. 当有IO事件就绪时, 就会处理这些IO事件
selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
//ioRatio表示:此线程分配给IO操作所占的时间比(即运行processSelectedKeys耗时在整个循环中所占用的时间).
final int ioRatio = this.ioRatio;
boolean ranTasks;
if (ioRatio == 100) {
try {
if (strategy > 0) {
//查询就绪的 IO 事件, 然后处理它;
processSelectedKeys();
}
} finally {
// Ensure we always run tasks.
//运行 taskQueue 中的任务.
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
//查询就绪的 IO 事件, 然后处理它;
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
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 方法做了 3 件事情:
- select 获取感兴趣的事件。
- processSelectedKeys 处理事件。
- runAllTasks 执行队列中的任务
上面的三个方法,select 方法(体现非阻塞)
核心 select 方法解析:
private int select(long deadlineNanos) throws IOException {
if (deadlineNanos == NONE) {
return selector.select();
}
// Timeout will only be 0 if deadline is within 5 microsecs
long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
说明:
- 调用 selector 的 select 方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上 0.5秒进行阻塞。当执行 execute 方法的时候,也就是添加任务的时候,唤醒 selecor,防止 selecotr 阻塞时间过长
3. EventLoop 核心的运行机制小结
-
每次执行 ececute 方法都是向队列中添加任务。
-
当第一次添加时就启动线程,执行 run 方法,而 run 方法是整个 EventLoop 的核心,就像 EventLoop 的名字一样,Loop Loop ,不停的 Loop ,Loop 做什么呢?做 3 件事情。
-
调用 selector 的 select 方法,默认阻塞一秒钟,如果有定时任务,则在定时任务剩余时间的基础上在加上 0.5秒进行阻塞。当执行 execute 方法的时候,也就是添加任务的时候,唤醒 selecor,防止 selecotr 阻塞时间过长。
-
当 selector 返回的时候,回调用 processSelectedKeys 方法对 selectKey 进行处理。
-
当 processSelectedKeys 方法执行结束后,则按照 ioRatio 的比例执行 runAllTasks 方法,默认是 IO 任务时间和非 IO 任务时间是相同的,你也可以根据你的应用特点进行调优 。比如 非 IO 任务比较多,那么你就将ioRatio 调小一点,这样非 IO 任务就能执行的长一点。防止队列积攒过多的任务。
参考文档
Netty学习和源码分析github地址
Netty从入门到精通视频教程(B站)
Netty权威指南 第二版