带着问题源码
- Netty 的 NioEventLoop 是如何实现的?它为什么能够保证 Channel 的操作是线程安全的?
- Netty 如何解决 JDK epoll 空轮询 Bug?
- NioEventLoop 是如何实现无锁化的?
一、作用与设计原理
Netty的NioEventLoop并不是一个存粹的I/O线程,除了负责I/O的读写外(用于处理 Channel 生命周期内的所有I/O事件,如accept、connect、read、write等I/O事件),还负责处理系统任务和延迟任务(定时任务);
主要就是做3个事:轮询 I/O 事件,处理 I/O 事件,处理异步任务队列
1.1 系统任务&延迟任务
NioEventLoop 内部有两个非常重要的异步任务队列,分别为普通任务队列和定时任务队列。
// 普通任务队列
private final Queue<Runnable> taskQueue;
默认使用的是 Mpsc Queue(多生产者单消费者队列)
static <T> Queue<T> newMpscQueue() {
return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscUnboundedArrayQueue<T>(MPSC_CHUNK_SIZE)
: new MpscUnboundedAtomicArrayQueue<T>(MPSC_CHUNK_SIZE);
}
多个外部线程可能会并发操作同一个Channel,用来保证线程的安全性
// 统计任务等收尾动作
private final Queue<Runnable> tailTasks
// 延迟队列
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
SCHEDULED_FUTURE_TASK_COMPARATOR,
11);
1.1.1 系统任务
通过execute(Runnable task)方法实现,目的:当I/O线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程封装成task放入到普通队列中,由I/O线程负责执行
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 判断thread == this.thread;是否为当前EventExecutor内部线程,可能为其他线程调用该方法
boolean inEventLoop = inEventLoop();
// 加入到普通队列
addTask(task);
if (!inEventLoop) {
// #AbstractChannel#write 可以写入非内部线程的任务
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (!offerTask(task)) {
reject(task);
}
}
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
1.1.2延迟任务
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 对应的方法实现,具体实现后续专讲
二 、线程执行源码( 4.1.42.Final源码)
protected void run() {
for (;;) {
try {
try {
// 如果存在就绪I/O事件那么会返回对应就绪Channel的数量>=0进入default条件
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 无任务,则进行轮询I/O事件
select(wakenUp.getAndSet(false)); // 轮询 I/O 事件
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
} catch (IOException e) {
// 重新构建Selector
rebuildSelector0();
handleLoopException(e);
continue;
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys(); // 处理 I/O 事件
} finally {
runAllTasks(); // 处理异步任务队列
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys(); // 处理 I/O 事件
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio); // 处理完 I/O 事件,再处理异步任务队列
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
2.1 轮询 I/O 事件
selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())
//实际调用
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
// NioEventLoop#selectNowSupplier
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
// 若存在任务,则调用Selector选择器中的提供的非阻塞方法,
// 执行后会立刻返回如果当前已经有就绪的Channel,会返回对应就绪Channel的数量否则返回0.
return selectNow();
}
}
// NioEventLoop#selectNow
int selectNow() throws IOException {
try {
return sele