在早期的Java语言中,我们使用最多的线程处理的主要方式无非是按需创建和启动新的 Thread 来执行并发的任务单元(即为每个任务单独创建一条线程处理),这种方式在高并发的场景下工作效率不高且资源浪费严重。随后引入了线程池技术,通过缓存和重用Thread 极大地提高了性能。
虽然池化和重用线程相对于简单地为每个任务都创建和销毁线程是一种进步,但是它并不能消除由上下文切换所带来的开销,其随着线程数量的增加很快变得明显。我们来看看Netty是如何解决这些问题来把每个线程的性能都压榨到极致。
NioEventLoop
Netty的 EventLoop 是协同设计的一部分,它采用了两个基本的 API:并发和网络编程。
在 Netty 中,一个 NioEventLoop 将由一个永远都不会改变的 Thread 驱动。
驱动线程的创建及绑定
还记得在创建 NioEventLoopGroup 时创建了一个 ThreadPerTaskExcutor 类型的 excutor吗?NioEventLoop 的驱动线程就是由该 excutor 创建的。接下来让我们看看它是如何创建的:
private void doStartThread() {
assert this.thread == null;
//ThreadPerTaskExcutor来创建
this.executor.execute(new Runnable() {
public void run(){
......
}
});
}
调用 ThreadPerTaskExcutor 的 excutor() 方法,直接创建一条线程并调用 start() 方法立即开启线程
public void execute(Runnable command) {
this.threadFactory.newThread(command).start();
}
线程开启后,会调用匿名内部类中在传进去 run() 方法的逻辑,在这里进行线程的绑定
public void run() {
//this是SingleThreadEventExecutor,即NioEventLoop的父类
//把thread属性赋值为当前线程,即通过 ThreadPerTaskExcutor 新创建的线程
SingleThreadEventExecutor.this.thread = Thread.currentThread();
if (SingleThreadEventExecutor.this.interrupted) {
SingleThreadEventExecutor.this.thread.interrupt();
}
boolean success = false;
SingleThreadEventExecutor.this.updateLastExecutionTime();
boolean var112 = false;
int oldState;
label1907: {
try {
var112 = true;
//调用 NioEventLoop的 run()方法开始循环处理事件
SingleThreadEventExecutor.this.run();
success = true;
var112 = false;
break label1907;
} catch (Throwable var119) {
.....
}finally {
.....
}
}
NioEventLoop任务执行方式
Netty中所采用的线程模型,是通过 NioEventLoop 所绑定的线程来处理该 NioEventLoop 中产生的所有事件。Netty 卓越的性能正是因为当前执行的 Thread 身份的确定,把该线程的性能压榨到极致,相比于线程池,省去了线程切换的时间和排队从阻塞队列获取任务的时间。
接下来我们来看一看其任务调度的具体实现,以 ChannelHandlerContext 中触发Handler的执行 channelRead() 的 fireChannelRead()方法为例:
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
//判断当前调用线程是否是NioEventLoop绑定的线程
if (executor.inEventLoop()) {
//如果是则立即执行
next.invokeChannelRead(m);
} else {
//如果不是则调用NioEventLoop的excute()方法放入taskQueue
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
如果当前调用的线程正是 NioEventLoop 绑定的线程,那么所提交的代码块将立即执行,否则将该任务放入其内部的 taskQueue 中
判断是否为绑定的线程代码如下
public boolean inEventLoop() {
return this.inEventLoop(Thread.currentThread());
}
---------------------------------
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
将任务放入 taskQueue 的代码如下:
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} else {
boolean inEventLoop = this.inEventLoop();
//放入taskQueue
this.addTask(task);
if (!inEventLoop) {
//如果当前绑定的线程还没创建,则利用ThreadPerTaskExcutor创建线程
this.startThread();
....
}
....
}
}