在查看源码前,先了解下Netty中的线程池EventLoopGroup
是如何执行任务的,因为源码中很多异步操作都是把任务提交到EventLoopGroup
中。
EventLoopGroup
EventLoopGroup
可以理解为一个线程池,以NioEventLoopGroup
为例查看继承体系。
Iterable
,可以通过迭代的方式查看里面的元素。Executor
,线程池的顶级接口,包含一个execute()
方法,用于提交任务到线程池中。ExecutorService
,继承了Executor
接口,并新增了一些方法,如submit()
方法、shutdown()
方法。ScheduledExecutorService
,继承了ExecutorService
接口,增加了定时任务相关的方法。
上面四个接口,后面三个类都是线程池相关的接口。
EventExecutorGroup
,继承了ScheduledExecutorService
,提供了next()
方法用于获取一个EventExecutor
。EventLoopGroup
,扩展自EventExecutorGroup
,并增加或修改了两大功能,一是提供了next()
方法用于获取一个EventLoop
,二是提供了注册Channel
到事件轮询器中。MultithreadEventLoopGroup
,抽象类,EventLoopGroup
的所有实现类都继承自这个类,可以看作是一种模板,从名字也可以看出来它里面包含多个线程来处理任务。NioEventLoopGroup
,具体实现类,使用 NIO 形式(多路复用中的 select)工作的EventLoopGroup
。更换前缀就可以得到不同的实现类,比如EpollEventLoopGroup
专门用于 Linux 平台,KQueueEventLoopGroup
专门用于 MacOS/BSD 平台。
EventLoop
EventLoop
可以理解为是 EventLoopGroup
中的工作线程,类似于Java
线程池中的工作线程,它里面包含了一个线程,控制着这个线程的生命周期。
以 NioEventLoop
为例看看它的继承体系:
-
EventExecutor
,扩展自EventLoopGroup
,主要增加了判断一个线程是不是在EventLoop
中的方法。 -
OrderedEventExecutor
,扩展自EventExecutor
,这是一个标记接口,标志着里面的任务都是按顺序执行的。 -
EventLoop
,扩展自EventLoopGroup
,它将为已注册进来的Channel
处理所有的 IO 事件,另外,它还扩展自OrderedEventExecutor
接口,说明里面的任务是按顺序执行的。 -
SingleThreadEventLoop
,抽象类,EventLoop
的所有实现类都继承自这个类,可以看作是一种模板,从名字也可以看出来它是使用单线程处理的。 -
NioEventLoop
,具体实现类,绑定到一个Selector
上,同时可以注册多个Channel
到Selector
上,同时,它继承自SingleThreadEventLoop
,也就说明了一个Selector
对应一个线程。同样地,更换前缀就可以得到不同的实现,比如EpollEventLoop
、KQueueEventLoop
。
NioEventLoop执行原理
以NioEventLoopGroup
为例,通过源码分析EventLoopGroup
是如何执行任务的。
public class Main {
public static void main(String[] args) throws IOException {
EventLoopGroup bossGroup = new NioEventLoopGroup(4);
// 这里的next()方法返回的是NioEventLoop实例
// execute()方法提交任务
bossGroup.next().execute(() -> {
System.out.println(Thread.currentThread() + ":" + LocalDateTime.now().toString());
});
System.in.read();
}
}
查看NioEventLoopGroup
的构造方法,最后会进入到父类MultithreadEventExecutorGroup
的构造方法。
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
// 这里的具体实现就是 NioEventLoop
private final EventExecutor[] children;
// EventExecutor 选择器, 通过next()方法从 children 列表里面选择一个 EventExecutor 执行任务
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
// 构造方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 线程数量必须大于0
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
// 这里的executor是用来创建线程的
if (executor == null) {
// newDefaultThreadFactory()返回的是io.netty.util.concurrent.DefaultThreadFactory,继承自 java.util.concurrent.ThreadFactory,
// NioEventLoop 里面的线程(FastThreadLocalThread)就是通过这个生成的
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); // ①
}
// 创建数组
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// newChild 方法由子类 NioEventLoopGroup 实现
// 返回一个 NioEventLoop
children[i] = newChild(executor, args); // ②
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
// 省略......
}
}
/**
* 线程选择器
* 如果线程数量是2的N次方, 则使用:PowerOfTwoEventExecutorChooser, 否则使用GenericEventExecutorChooser
* chooser是用来从children里面选择EventExecutor(这里的具体实现是NioEventLoop), 前者做了优化, 更加高效
* 前者: executors[idx.getAndIncrement() & executors.length - 1]
* 后者: executors[(int) Math.abs(idx.getAndIncrement() % executors.length)]
*/
chooser = chooserFactory.newChooser(children);
// 省略......
}
}
①ThreadPerTaskExecutor
是用来创建线程的,通过execute
方法创建线程并执行任务,这里面创建的线程是Netty实现的FastThreadLocalThread
。
public final class ThreadPerTaskExecutor implements Executor {
// 线程工厂
private final ThreadFactory threadFactory;
// 构造函数传线程工厂
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
}
// 新建线程并执行传进来的任务
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
②创建NioEventLoop
public class NioEventLoopGroup extends MultithreadEventLoopGroup {
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
}
查看上面创建NioEventLoop
实例的构造方法,里面主要有下面几个重要参数。
public final class NioEventLoop extends SingleThreadEventLoop {
// Java NIO 中的 java.nio.channels.Selector
// 用来绑定Socket事件
private Selector selector;
private Selector unwrappedSelector;
/**
* 父类 SingleThreadEventExecutor 中
* 有个任务队列, 用来存放提交的任务
* private final Queue<Runnable> taskQueue;
*
* 具体实现是 ThreadPerTaskExecutor, 用来创建线程并执行任务
* private final Executor executor;
*
* 通过 ThreadPerTaskExecutor 创建出来的 FastThreadLocalThread
* private volatile Thread thread;
*/
}
NioEventLoop
是如何执行任务的?在这里打个断点,跟踪源码进去查看
bossGroup.next().execute(() -> {
System.out.println(Thread.currentThread() + ":" + LocalDateTime.now().toString());
});
NioEventLoop
执行任务的入口在父类SingleThreadEventExecutor
的execute
方法
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
// 任务队列
private final Queue<Runnable> taskQueue;
// 执行线程
private volatile Thread thread;
// 具体实现是 ThreadPerTaskExecutor
private final Executor executor;
// 1. NioEventLoop执行任务的入口
@Override
public void execute(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
private void execute(Runnable task, boolean immediate) {
// 判断调用线程是否等于内部线程 this.thread == Thread.currentThread()
boolean inEventLoop = inEventLoop();
// 放入任务队列
addTask(task);
if (!inEventLoop) {
// inEventLoop为false, 外部线程调用NioEventLoop
// 启动线程
startThread();
// 省略......
}
// 省略......
}
private void startThread() {
// 这里会判断, 只能启动一次
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);
}
}
}
}
}
private void doStartThread() {
assert thread == null;
// 这里执行的是ThreadPerTaskExecutor.execute()方法, 会创建线程并执行传进去的Runnable任务
executor.execute(new Runnable() {
@Override
public void run() {
// Thread.currentThread()返回的是上面调用ThreadPerTaskExecutor.execute()方法生成的线程
thread = Thread.currentThread();
// 省略......
try {
// 这里会执行NioEventLoop里面的run方法
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// 省略......
}
}
});
}
}
run()
NioEventLoop
的run
方法里面是一个死循环,里面负责监听Selector
上的IO事件和执行提交进来的异步任务。
public final class NioEventLoop extends SingleThreadEventLoop {
private Selector selector;
private Selector unwrappedSelector;
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
int selectNow() throws IOException {
return selector.selectNow();
}
@Override
protected void run() {
// 记录发生空转的次数, 既没有IO需要处理、也没有执行任何任务
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
// hasTasks() 是判断任务队列是否有任务
// 如果 hasTasks()为true, 则返回selectNowSupplier.get(), 否则返回 SelectStrategy.SELECT
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE: // -2
continue;
case SelectStrategy.BUSY_WAIT: // -3
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT: // -1
// 获取下一个定时任务执行时间
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
// 再次判断有没有任务
if (!hasTasks()) {
// key 调用select
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
// 出现异常, 重新构建Selector并注册事件
rebuildSelector0();
selectCnt = 0;
handleLoopException(e);
continue;
}
// 计算器加1
selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
// 控制处理执行io事件时间的占用比例, 默认是百分之50
// 一半时间用来处理io事件, 一半时间用来处理任务队列taskQueue里面的任务
final int ioRatio = this.ioRatio;
boolean ranTasks;
if (ioRatio == 100) {
try {
if (strategy > 0) {
// 处理IO事件
processSelectedKeys();
}
} finally {
// Ensure we always run tasks.
// key 执行taskQueue中全部的任务
ranTasks = runAllTasks();
}
} else if (strategy > 0) { // 如果 ioRatio != 100, 优先处理IO事件
final long ioStartTime = System.nanoTime();
try {
// 处理IO事件
processSelectedKeys();
} finally {
// 计算处理IO花费的时间
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
// key ioTime * (100 - ioRatio) / ioRatio 是计算任务队列执行的时间
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
// 0 代表运行最小数量的任务, 即运行63个任务
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
// 执行了任务队列的任务或者是有IO事件
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;
// 如果(!ranTasks && strategy <= 0) 即任务队里面没有任务, 也没有IO事件
// 则会执行 unexpectedSelectorWakeup(selectCnt)
// 如果 selectCnt 达到一定最大值(默认为512), 重新构建Selector并注册事件, 防止 JDK BUG空轮训
// 这个BUG好像是虽然调用了阻塞的selector.select()
// 但是由于操作系统底层发现socket断开,还是会返回0,然后又没能处理相应的事件
// 而且任务队列也为空的情况下,就会死循环下去,造成CPU100%
// Netty的解决方案就是用了一个变量selectCnt统计轮询的次数。
}
} 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 (Error) 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 (Error) e;
} catch (Throwable t) {
handleLoopException(t);
}
}
}
}
}
select(long deadlineNanos)
根据deadlineNanos的值选择是无限期阻塞还是阻塞一段时间直接返回
public final class NioEventLoop extends SingleThreadEventLoop {
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;
// 不阻塞的selectNow()和阻塞一段时间的select(timeout)
return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
}
runAllTasks(long timeoutNanos)
timeoutNanos为0时,最多只能执行63个任务。
timeoutNanos大于0时,如果已执行任务的数量加1是64的整数倍,执行时间超过timeoutNanos则先停下来返回。
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
protected boolean runAllTasks(long timeoutNanos) {
// 这里会将定时任务队列里面的可执行任务拿出来放到任务队列里
fetchFromScheduledTaskQueue();
// 从任务队列里面获取任务
Runnable task = pollTask();
if (task == null) {
// 去执行tailTasks的任务, 暂时还不知道这个是什么情况添加的任务
afterRunningAllTasks();
return false;
}
// 截至时间, 已经花费调度任务的时间+超时时间
// ScheduledFutureTask.nanoTime() 表示从开始到现在执行任务持续的时间
final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
// 记录执行任务数量
long runTasks = 0;
long lastExecutionTime;
for (;;) {
// 执行任务
safeExecute(task);
// 执行任务数量加1
runTasks ++;
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
if ((runTasks & 0x3F) == 0) {
// 当执行任务到达一定量时计算下时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
// 如果调度任务的时间超过截止时间了, 那就退出了, 否则时间太长了
if (lastExecutionTime >= deadline) {
break;
}
}
// 继续获取任务
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
// 这里会执行tailTasks里面的任务
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
}
runAllTasks()
将任务队列的任务全部执行完后再返回。
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false;
do {
// 这里会将定时任务队列里面的可执行任务拿出来放到任务队列里
fetchedAll = fetchFromScheduledTaskQueue();
// 执行队列里面的所有任务
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
// while循环保证定时任务都能被执行完
if (ranAtLeastOne) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
// 这里会执行tailTasks里面的任务
afterRunningAllTasks();
return ranAtLeastOne;
}
}
总结
- 每一个
NioEventLoop
都有一个自己的java.nio.channels.Selector
,可以往这个Selector
上注册我们感兴趣的事件。 - 每一个
NioEventLoop
都有一个自己的任务队列,提交的任务先放入到这个队列。 - 每一个
NioEventLoop
都有一个线程Thread
。 - 任务队列只有一个线程消费任务。
- Epoll的空轮询BUG解决方案
重要组件说明
ChannelHandler
ChannelHandler
是核心业务处理接口,用于处理或拦截IO
事件,并将其转发到ChannelPipeline
中的下一个ChannelHandler
,运用的是责任链设计模式。
ChannelHandler
分为入站和出站两种:ChannelInboundHandler
和ChannelOutboundHandler
,一般不建议直接实现这两个接口,而是它们的抽象类:
SimpleChannelInboundHandler
:处理入站事件,它可以帮我们做资源的自动释放等操作。不建议直接使用ChannelInboundHandlerAdapter
。ChannelOutboundHandlerAdapter
:处理出站事件。ChannelDuplexHandler
:既可以处理入站也可以处理出站。
ChannelHandlerContext
默认实现DefaultChannelHandlerContext
,里面有ChannelHandler
和ChannelPipeline
的引用。
往ChannelPipeline
中的节点就是一个个ChannelHandlerContext
。
ChannelPipeline
ChannelPipeline
是 ChannelHandler
的集合,它负责处理和拦截入站和出站的事件和操作,每个Channel
都有一个ChannelPipeline
与之对应,会自动创建。
ChannelPipeline
中存储的是ChannelHandlerContext
链,通过这个链把ChannelHandler
连接起来。
- 一个
Channel
对应一个ChannelPipeline
- 一个
ChannelPipeline
包含一条双向的ChannelHandlerContext
链 - 一个
ChannelHandlerContext
中包含一个ChannelHandler
- 一个
Channel
会绑定到一个EventLoop
上 - 一个
NioEventLoop
维护了一个Selector
(使用的是 Java 原生的Selector
) - 一个
NioEventLoop
相当于一个线程
Netty版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.63.Final</version>
</dependency>