EventLoop 绑定到 Channel 的过程详解
问题:channel.eventLoop() 返回的 EventLoop 是什么时候初始化的?
在 Bootstrap.doConnect() 方法中有这样一行代码:
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.connect(remoteAddress, connectPromise);
}
});
这里的 channel.eventLoop() 返回的 EventLoop 实例是在 register 过程中 绑定到 Channel 的。
一、EventLoop 绑定的完整流程
1.1 时间线
Bootstrap.connect()
↓
doResolveAndConnect()
↓
initAndRegister() ← 【这里完成 EventLoop 的绑定】
├─→ channelFactory.newChannel() // 创建 Channel
├─→ init(channel) // 初始化 Channel
└─→ group().register(channel) // 注册 Channel,绑定 EventLoop
↓
doResolveAndConnect0()
↓
doConnect()
↓
channel.eventLoop().execute() ← 【这里使用已绑定的 EventLoop】
1.2 关键点
- EventLoop 绑定发生在 register 阶段
- 在 doConnect() 调用时,EventLoop 已经绑定好了
- 一个 Channel 一旦绑定了 EventLoop,就不会再改变
二、详细代码追踪
2.1 入口:initAndRegister()
// AbstractBootstrap
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 1. 创建 Channel(此时 eventLoop 还是 null)
channel = channelFactory.newChannel();
// 2. 初始化 Channel(配置 pipeline 等)
init(channel);
} catch (Throwable t) {
// ...
}
// 3. 【关键】注册 Channel 到 EventLoopGroup,这里会绑定 EventLoop
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
此时 Channel 的状态:
channel.eventLoop=null(还未绑定)
2.2 选择 EventLoop:group().register(channel)
// MultithreadEventLoopGroup
@Override
public ChannelFuture register(Channel channel) {
// 【关键】从 EventLoopGroup 中选择一个 EventLoop
// next() 方法会使用 EventExecutorChooser 选择策略(轮询或幂次方优化)
return next().register(channel);
}
next() 方法的作用:
- 从 EventLoopGroup 中选择一个 EventLoop
- 使用轮询策略(如果线程数是 2 的幂次方,使用位运算优化)
- 返回选中的 EventLoop 实例
示例:
// 假设创建了 4 个 EventLoop
EventLoopGroup group = new NioEventLoopGroup(4);
// 第一次调用 next(),返回 eventLoop[0]
// 第二次调用 next(),返回 eventLoop[1]
// 第三次调用 next(),返回 eventLoop[2]
// 第四次调用 next(),返回 eventLoop[3]
// 第五次调用 next(),返回 eventLoop[0](循环)
2.3 EventLoop 注册 Channel:eventLoop.register(channel)
// SingleThreadEventLoop
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
// 【关键】调用 Channel 的 unsafe.register()
// 注意:这里把 this(当前 EventLoop)传进去了
promise.channel().unsafe().register(this, promise);
return promise;
}
关键点:
- 这里的
this就是从 EventLoopGroup 中选出来的 EventLoop 实例 - 将这个 EventLoop 传递给 Channel 的 unsafe.register() 方法
2.4 绑定 EventLoop:unsafe.register(eventLoop, promise)
// AbstractChannel.AbstractUnsafe
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// ...省略部分代码
// 【核心】将 EventLoop 绑定到 Channel
// 从此以后,这个 Channel 就属于这个 EventLoop 了
AbstractChannel.this.eventLoop = eventLoop;
// 判断当前线程是否是 EventLoop 的线程
if (eventLoop.inEventLoop()) {
// 如果是,直接执行 register0
register0(promise);
} else {
// 如果不是,提交任务到 EventLoop 的任务队列
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
// ...
}
}
}
关键代码:
AbstractChannel.this.eventLoop = eventLoop;
这一行代码完成了 EventLoop 的绑定!
此时 Channel 的状态:
channel.eventLoop= 从 EventLoopGroup 中选出的 EventLoop 实例- 这个绑定关系是永久的,不会改变
2.5 后续使用:channel.eventLoop()
// AbstractChannel
@Override
public EventLoop eventLoop() {
EventLoop eventLoop = this.eventLoop;
if (eventLoop == null) {
throw new IllegalStateException("channel not registered to an event loop");
}
return eventLoop;
}
作用:
- 返回在 register 阶段绑定的 EventLoop 实例
- 如果还未绑定,抛出异常
三、完整流程图
用户代码: bootstrap.connect("localhost", 8080)
↓
Bootstrap.doResolveAndConnect()
↓
Bootstrap.initAndRegister()
↓
channelFactory.newChannel()
[创建 NioSocketChannel]
[此时 channel.eventLoop = null]
↓
init(channel)
[初始化 Pipeline]
↓
config().group().register(channel)
↓
MultithreadEventLoopGroup.next()
[从 EventLoopGroup 中选择一个 EventLoop]
[假设选中了 eventLoop[2]]
↓
eventLoop[2].register(channel)
↓
SingleThreadEventLoop.register(promise)
↓
channel.unsafe().register(eventLoop[2], promise)
↓
AbstractUnsafe.register(eventLoop, promise)
↓
【核心】AbstractChannel.this.eventLoop = eventLoop
[绑定完成!channel.eventLoop = eventLoop[2]]
↓
eventLoop.execute(register0 任务)
[提交 register0 任务到 eventLoop[2] 的任务队列]
↓
[register 完成,返回到 doResolveAndConnect()]
↓
doResolveAndConnect0()
↓
doConnect()
↓
channel.eventLoop().execute(connect 任务)
[使用已绑定的 eventLoop[2]]
[提交 connect 任务到 eventLoop[2] 的任务队列]
四、关键问题解答
Q1:EventLoop 是什么时候创建的?
A:EventLoop 在创建 EventLoopGroup 时就已经创建好了。
// 用户代码
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
// 这一行代码会创建 4 个 NioEventLoop 实例
// 每个 NioEventLoop 内部包含:
// - 一个 Selector
// - 一个任务队列(taskQueue)
// - 一个定时任务队列(scheduledTaskQueue)
// - 但此时还没有创建 Thread(线程是懒加载的)
Q2:EventLoop 的线程是什么时候创建的?
A:线程是在第一次提交任务时创建的(懒加载)。
// 在 AbstractUnsafe.register() 中
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
// 这是第一次向 EventLoop 提交任务
// 此时会触发线程的创建(在 SingleThreadEventExecutor.execute() 中)
Q3:一个 Channel 可以绑定多个 EventLoop 吗?
A:不可以。一个 Channel 只能绑定一个 EventLoop,而且一旦绑定就不会改变。
原因:
- 保证线程安全:所有 IO 操作都在同一个线程中执行
- 保证事件顺序:事件按提交顺序执行
- 简化编程模型:无需考虑并发问题
Q4:一个 EventLoop 可以绑定多个 Channel 吗?
A:可以。一个 EventLoop 可以管理多个 Channel。
示例:
EventLoopGroup (4 个 EventLoop)
├─ EventLoop[0]
│ ├─ Channel A
│ ├─ Channel B
│ └─ Channel C
├─ EventLoop[1]
│ ├─ Channel D
│ └─ Channel E
├─ EventLoop[2]
│ └─ Channel F
└─ EventLoop[3]
├─ Channel G
├─ Channel H
└─ Channel I
Q5:如何选择 EventLoop?
A:使用 EventExecutorChooser 选择策略。
// MultithreadEventLoopGroup 构造函数中
chooser = chooserFactory.newChooser(children);
// 两种策略:
// 1. PowerOfTwoEventExecutorChooser(线程数是 2 的幂次方)
// 使用位运算:index = idx.getAndIncrement() & (executors.length - 1)
//
// 2. GenericEventExecutorChooser(线程数不是 2 的幂次方)
// 使用取模:index = Math.abs(idx.getAndIncrement() % executors.length)
五、代码示例
5.1 验证 EventLoop 绑定
public class EventLoopBindingDemo {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup(4);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
// 在这里可以获取到绑定的 EventLoop
EventLoop eventLoop = ch.eventLoop();
System.out.println("Channel 绑定的 EventLoop: " + eventLoop);
System.out.println("EventLoop 的线程: " + eventLoop.inEventLoop());
}
});
// 连接多次,观察 EventLoop 的分配
for (int i = 0; i < 10; i++) {
ChannelFuture future = bootstrap.connect("localhost", 8080);
future.addListener(f -> {
if (f.isSuccess()) {
Channel channel = ((ChannelFuture) f).channel();
System.out.println("连接 " + i + " 使用的 EventLoop: " + channel.eventLoop());
}
});
}
Thread.sleep(5000);
group.shutdownGracefully();
}
}
输出示例:
连接 0 使用的 EventLoop: NioEventLoop@1234 (thread: nioEventLoopGroup-2-1)
连接 1 使用的 EventLoop: NioEventLoop@5678 (thread: nioEventLoopGroup-2-2)
连接 2 使用的 EventLoop: NioEventLoop@9abc (thread: nioEventLoopGroup-2-3)
连接 3 使用的 EventLoop: NioEventLoop@def0 (thread: nioEventLoopGroup-2-4)
连接 4 使用的 EventLoop: NioEventLoop@1234 (thread: nioEventLoopGroup-2-1)
...
可以看到,EventLoop 是轮询分配的。
六、时序图
用户线程 EventLoopGroup EventLoop[2] Channel
| | | |
|--connect()------------->| | |
| | | |
|--initAndRegister()----->| | |
| | | |
| |--newChannel()--------->| |
| | | |
| |<-----------------------| |
| | (channel) | |
| | | |
| |--next()--------------->| |
| | (选择 EventLoop[2]) | |
| | | |
| |<-----------------------| |
| | (eventLoop[2]) | |
| | | |
| |--register(channel)---->| |
| | | |
| | |--unsafe.register()-->|
| | | (eventLoop[2]) |
| | | |
| | | 【绑定】
| | | channel.eventLoop = eventLoop[2]
| | | |
| | |<----------------------|
| | | |
|<--regFuture-------------| | |
| | | |
|--doConnect()----------->| | |
| | | |
| | |<--eventLoop()---------|
| | | (返回 eventLoop[2]) |
| | | |
| | |<--execute(task)-------|
| | | (提交 connect 任务) |
| | | |
七、总结
7.1 EventLoop 绑定的关键点
- 绑定时机:在
register阶段,具体在AbstractUnsafe.register()方法中 - 绑定代码:
AbstractChannel.this.eventLoop = eventLoop - 选择策略:从 EventLoopGroup 中轮询选择一个 EventLoop
- 绑定关系:一个 Channel 只能绑定一个 EventLoop,且永久不变
- 使用时机:在
doConnect()等方法中通过channel.eventLoop()获取
7.2 为什么要绑定 EventLoop?
- 线程安全:所有 IO 操作都在同一个线程中执行,无需加锁
- 事件顺序:保证事件按提交顺序执行
- 性能优化:避免线程切换和上下文切换
- 简化编程:用户无需考虑并发问题
7.3 EventLoop 的生命周期
创建 EventLoopGroup
↓
创建多个 EventLoop(但还没有线程)
↓
Channel 注册时,选择一个 EventLoop 并绑定
↓
第一次提交任务时,创建线程
↓
线程启动,进入 run() 循环
↓
处理 IO 事件和任务队列
↓
EventLoopGroup 关闭时,线程退出
1276



被折叠的 条评论
为什么被折叠?



