開篇
NioEventLoop是Netty框架的Reactor線程;
NioEventLoop負(fù)責(zé)處理注冊(cè)在其上面的所有Channel的IO事件,通常情況下一個(gè)NioEventLoop會(huì)下掛多個(gè)Channel;
NioEventLoop同時(shí)會(huì)負(fù)責(zé)通過execute方法提交的任務(wù),以及通過schedule方法提交的定時(shí)任務(wù);
在接下來幾篇文章,我會(huì)通過Netty的源碼深入講解NioEventLoop的實(shí)現(xiàn)機(jī)制。
特別說明:基于4.1.52版本的源碼
類繼承關(guān)系以及重要的成員變量
先來看下NioEventLoop的類關(guān)系圖和重要的屬性,對(duì)其有一個(gè)整體的感知,便于后面詳細(xì)分析。
- 類繼承關(guān)系
NioEventLoop繼承體系.png
可以看到NioEventLoop的繼承關(guān)系非常復(fù)雜,最上層是JDK的Executor
接口,說明它歸根到底是一個(gè)執(zhí)行器,是用來執(zhí)行任務(wù)的。另外,它實(shí)現(xiàn)了EventLoop
接口、EventExecutorGroup
接口和ScheduledExecutorService
接口,繼承了SingleThreadEventExecutor
類,這些接口和類為這個(gè)執(zhí)行器添加了十分繁復(fù)的功能特性,要搞清楚NioEventLoop的具體實(shí)現(xiàn)機(jī)制就要不停的在這些父類和接口中來回跳轉(zhuǎn)。 - 重要的成員變量
private Selector selector;
private SelectedSelectionKeySet selectedKeys;
private volatile Thread thread;
private final EventExecutorGroup parent;
private final Queue<Runnable> taskQueue;
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
private final Queue<Runnable> tailTasks;
-
selector
:作為NIO框架的Reactor線程,NioEventLoop需要處理網(wǎng)絡(luò)IO事件,因此它需要有一個(gè)多路復(fù)用器,即Java NIO的Selector對(duì)象; -
selectedKeys
:每次select操作選出來的有事件就緒的SelectionKey集合,在NioEventLoop的run
方法中會(huì)處理這些事件; -
thread
:即每個(gè)NioEventLoop綁定的線程,它們是一對(duì)一的關(guān)系,一旦綁定,在整個(gè)生命周期內(nèi)都不會(huì)改變; -
parent
:即當(dāng)前的NioEventLoop所屬的EventExecutorGroup; -
taskQueue
:NioEventLoop中三大隊(duì)列之一,用于保存需要被執(zhí)行的任務(wù)。 -
scheduledTaskQueue
:NioEventLoop中三大隊(duì)列之一,是一個(gè)優(yōu)先級(jí)隊(duì)列(內(nèi)部其實(shí)是一個(gè)按照任務(wù)的下次執(zhí)行時(shí)間排序的小頂堆),用于保存定時(shí)任務(wù),當(dāng)檢測(cè)到定時(shí)任務(wù)需要被執(zhí)行時(shí),會(huì)將任務(wù)從scheduledTaskQueue
中取出,放入taskQueue
; -
tailTasks
:NioEventLoop中三大隊(duì)列之一,用于存儲(chǔ)當(dāng)前或下一次事件循環(huán)結(jié)束后需要執(zhí)行的任務(wù);
構(gòu)造函數(shù)
首先來看NioEventLoop的構(gòu)造函數(shù)
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory) {
// 設(shè)置parent、executor、addTaskWakesUp(添加任務(wù)時(shí)是否喚醒select)、創(chuàng)建taskQueue和tailTask隊(duì)
// 列
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
rejectedExecutionHandler);
this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
// selector初始化
final SelectorTuple selectorTuple = openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
在構(gòu)造函數(shù)中,會(huì)創(chuàng)建任務(wù)隊(duì)列和tailTask隊(duì)列
private static Queue<Runnable> newTaskQueue(
EventLoopTaskQueueFactory queueFactory) {
if (queueFactory == null) {
return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
}
return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
}
private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue()
: PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);
}
默認(rèn)情況下,會(huì)創(chuàng)建MPSC,即多生產(chǎn)者單消費(fèi)者的隊(duì)列,這里最終會(huì)用到JCTools庫,這里不過多介紹,感興趣的可以自己去了解。
構(gòu)造函數(shù)中還會(huì)初始化selector和根據(jù)配置對(duì)selectedKeys進(jìn)行優(yōu)化
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
// 如果優(yōu)化選項(xiàng)沒有開啟,則直接返回
if (DISABLE_KEY_SET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
}
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
});
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
如果設(shè)置了優(yōu)化開關(guān)(默認(rèn)優(yōu)化選項(xiàng)是開啟的),則通過反射的方式從Selector中獲取selectedKeys和publicSelectedKeys,將這兩個(gè)成員設(shè)置為可寫,通過反射,使用Netty構(gòu)造的selectedKeySet將原生JDK的selectedKeys替換掉。
我們知道使用Java原生NIO接口時(shí),需要先調(diào)Selector的select方法,再調(diào)selectedKeys方法才可以獲得有IO事件準(zhǔn)備好的SelectionKey集合。這里優(yōu)化過后,只通過一步select調(diào)用,就可以從selectedKeySet獲得需要的SelectionKey集合。
另外,原生Java的SelectionKey集合是一個(gè)HashSet,這里優(yōu)化過后的SelectedSelectionKeySet底層是一個(gè)數(shù)組,效率更高。
run方法解析
EventLoop的職責(zé)可以用下面這張圖形象的表示
EventLoop的
run
方法在一個(gè)for死循環(huán)中,周而復(fù)始的做著三件事:
1、從已注冊(cè)的Channel監(jiān)聽I(yíng)O事件;
2、處理IO事件;
3、從任務(wù)隊(duì)列取任務(wù)執(zhí)行。
protected void run() {
int selectCnt = 0;
for (;;) {
int strategy;
try {
// 計(jì)算本次循環(huán)的執(zhí)行策略
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 調(diào)用Java NIO的多路復(fù)用器,檢查注冊(cè)在NioEventLoop上的Channel的IO狀態(tài)
strategy = select(curDeadlineNanos);
}
} catch (IOException e) {
}
// 處理IO事件
processSelectedKeys();
// 處理任務(wù)隊(duì)列中的任務(wù)
ranTasks = runAllTasks();
...
}
下面詳細(xì)解析:
- 先來看calculateStrategy
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
protected boolean hasTasks() {
assert inEventLoop();
return !taskQueue.isEmpty() || !tailTasks.isEmpty();
}
每次循環(huán),都會(huì)檢測(cè)任務(wù)隊(duì)列和IO事件,如果任務(wù)隊(duì)列中沒有任務(wù),則直接返回SelectStrategy.SELECT;如果任務(wù)隊(duì)列中有任務(wù),則會(huì)調(diào)用非阻塞的selectNow
檢測(cè)有IO事件準(zhǔn)備好的Channel數(shù)。
-
阻塞的select
當(dāng)任務(wù)隊(duì)列中沒有任務(wù)時(shí),直接進(jìn)入select分支
case SelectStrategy.SELECT:
// 找到下一個(gè)將要執(zhí)行的定時(shí)任務(wù)的截止時(shí)間
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
// 阻塞調(diào)用select
strategy = select(curDeadlineNanos);
}
} finally {
nextWakeupNanos.lazySet(AWAKE);
}
// fall through
nextScheduledTaskDeadlineNanos
方法返回下一個(gè)將要被執(zhí)行的定時(shí)任務(wù)的截止時(shí)間
protected final long nextScheduledTaskDeadlineNanos() {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
return scheduledTask != null ? scheduledTask.deadlineNanos() : -1;
}
final ScheduledFutureTask<?> peekScheduledTask() {
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
return scheduledTaskQueue != null ? scheduledTaskQueue.peek() : null;
}
NioEventLoop的定時(shí)任務(wù)隊(duì)列是一個(gè)優(yōu)先級(jí)隊(duì)列,隊(duì)列中存儲(chǔ)的是ScheduledFutureTask對(duì)象
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) {
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
SCHEDULED_FUTURE_TASK_COMPARATOR,
11);
}
return scheduledTaskQueue;
}
通過ScheduledFutureTask的compareTo
方法可以看出,優(yōu)先級(jí)隊(duì)列中的元素是以任務(wù)的截止時(shí)間來排序的,隊(duì)首元素的截止時(shí)間最小,當(dāng)截止時(shí)間相同時(shí),以任務(wù)ID排序,ID小的排在前面。
public int compareTo(Delayed o) {
if (this == o) {
return 0;
}
ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
long d = deadlineNanos() - that.deadlineNanos();
if (d < 0) {
return -1;
} else if (d > 0) {
return 1;
} else if (id < that.id) {
return -1;
} else {
assert id != that.id;
return 1;
}
}
當(dāng)定時(shí)任務(wù)ScheduledFutureTask執(zhí)行后,會(huì)根據(jù)periodNanos
的取值決定是否要將任務(wù)重新放回隊(duì)列。從netty的注釋可以清晰看到:
當(dāng)
periodNanos
為0時(shí),表示的是只執(zhí)行一次的任務(wù),執(zhí)行完后丟棄就好,不再放回隊(duì)列;
當(dāng)periodNanos
大于0時(shí)表示的是以固定的頻率執(zhí)行任務(wù),下一次任務(wù)執(zhí)行的開始時(shí)間是以上一次任務(wù)的開始時(shí)間為基準(zhǔn)而得來;
當(dāng)periodNanos
小于0時(shí)表示的是以固定的延遲時(shí)間執(zhí)行任務(wù),下一次任務(wù)的開始時(shí)間是以上一次任務(wù)的結(jié)束時(shí)間為基準(zhǔn)而得來。
/* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */
private final long periodNanos;
看下ScheduledFutureTask的run
方法
public void run() {
assert executor().inEventLoop();
try {
if (delayNanos() > 0L) {
// 執(zhí)行時(shí)間還未到,則要么移除任務(wù),要么重新加入隊(duì)列
if (isCancelled()) {
scheduledExecutor().scheduledTaskQueue().removeTyped(this);
} else {
scheduledExecutor().scheduleFromEventLoop(this);
}
return;
}
// 只執(zhí)行一次的任務(wù),執(zhí)行完后丟棄就好,不再放回隊(duì)列
if (periodNanos == 0) {
if (setUncancellableInternal()) {
V result = runTask();
setSuccessInternal(result);
}
} else {
if (!isCancelled()) {
runTask();
if (!executor().isShutdown()) {
// 根據(jù)periodNanos ,計(jì)算截止時(shí)間
if (periodNanos > 0) {
deadlineNanos += periodNanos;
} else {
deadlineNanos = nanoTime() - periodNanos;
}
if (!isCancelled()) {
// 重新加入隊(duì)列
scheduledExecutor().scheduledTaskQueue().add(this);
}
}
}
}
} catch (Throwable cause) {
setFailureInternal(cause);
}
}
當(dāng)任務(wù)的執(zhí)行時(shí)間還未到,則判斷任務(wù)是否已經(jīng)取消,如果已取消則移除任務(wù),否則重新加入隊(duì)列。對(duì)于只執(zhí)行一次的任務(wù),執(zhí)行完了不會(huì)再放回隊(duì)列。其他的任務(wù),則根據(jù)periodNanos
的類型,重新計(jì)算截止時(shí)間,重新放回隊(duì)列,等待下次調(diào)度。
定時(shí)任務(wù)的優(yōu)先級(jí)隊(duì)列到此介紹完畢,接著看NioEventLoop的run
方法
nextWakeupNanos.set(curDeadlineNanos);
try {
// 再次判斷任務(wù)隊(duì)列中是否有任務(wù)
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
}
private int select(long deadlineNanos) throws IOException {
// 如果沒有定時(shí)任務(wù),直接調(diào)Java NIO的select,進(jìn)入阻塞
if (deadlineNanos == NONE) {
return selector.select();
}
// 如果截止時(shí)間小于0.5ms,則timeoutMillis 為0,直接調(diào)非阻塞的selectNow()方法
long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
在調(diào)用select
之前,再次調(diào)用hasTasks()
判斷從上次調(diào)用該方法到目前為止是否有任務(wù)加入,多做了一層防護(hù),因?yàn)檎{(diào)用select
時(shí),可能會(huì)阻塞,這時(shí),如果任務(wù)隊(duì)列中有任務(wù)就會(huì)長(zhǎng)時(shí)間得不到執(zhí)行,所以須小心謹(jǐn)慎。
如果任務(wù)隊(duì)列中還是沒有任務(wù),則會(huì)調(diào)用select
方法。在這個(gè)方法中會(huì)根據(jù)入?yún)?code>deadlineNanos來選擇調(diào)用NIO的哪個(gè)select方法:
如果
deadlineNanos
為NONE,即沒有定時(shí)任務(wù)時(shí),直接調(diào)用NIO的無參select
方法,進(jìn)入永久阻塞,除非檢測(cè)到Channel的IO事件或者被wakeup;
如果存在定時(shí)任務(wù),且定時(shí)任務(wù)的截止時(shí)間小于0.5ms,則timeoutMillis 為0,直接調(diào)非阻塞的selectNow
方法,也就是說馬上有定時(shí)任務(wù)需要執(zhí)行了,不要再進(jìn)入阻塞了;
其他情況,調(diào)用select(timeout)
,進(jìn)入有超時(shí)時(shí)間的阻塞。
到這里,可能有人要問了:在上面的方法中,如果調(diào)用了Java NIO的無參的select
方法,就會(huì)進(jìn)入阻塞,除非檢測(cè)到Channel的IO事件,那么在檢測(cè)到IO事件之前,加入到任務(wù)隊(duì)列中的任務(wù)怎么得到執(zhí)行呢?
好,你想,在檢測(cè)到IO事件之前,可以退出阻塞的方法是什么?對(duì),調(diào)用wakeup
方法。那么我們來搜一下NioEventLoop中有調(diào)用Selector的wakeup
方法的地方嗎:
protected void wakeup(boolean inEventLoop) {
if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
selector.wakeup();
}
}
還真搜到了,再看一下這個(gè)方法被調(diào)用的地方
看到SingleThreadEventExecutor的execute
方法了嗎,就是說在調(diào)execute
方法,向EventLoop提交任務(wù)時(shí),會(huì)將EventLoop線程從Java NIO的select阻塞中喚醒。
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
...
}
if (!addTaskWakesUp && immediate) {
// 喚醒EventLoop線程,執(zhí)行任務(wù)隊(duì)列中的任務(wù)
wakeup(inEventLoop);
}
}
到這里,NioEventLoop的run方法的職責(zé)之一:檢測(cè)Channel的IO事件就講解完畢。
至于IO事件的處理以及任務(wù)隊(duì)列中任務(wù)的處理會(huì)在后面的文章中解析,敬請(qǐng)期待。
總結(jié)
在本文中,對(duì)Netty的NioEventLoop進(jìn)行了深入的解讀,并且詳細(xì)講解了它的三大職責(zé)之一:檢測(cè)Channel的IO事件的機(jī)制。
NioEventLoop是Netty最核心的概念,內(nèi)部運(yùn)行機(jī)制很復(fù)雜,在接下來的兩篇文章中會(huì)繼續(xù)分析。