Netty源碼------NioEventLoop
目錄
1、初識 NioEventLoop
1.1 先來簡單回顧一下Netty模型
- Netty中的Channel系列類型,對應於經典Reactor模型中的client, 封裝了用戶的通訊連接。
- Netty中的EventLoop系列類型,對應於經典Reactor模型中的Reactor,完成Channel的註冊、輪詢、分發。
- Netty中的Handler系列類型,對應於經典Reactor模型中的Handler,不過Netty中的Handler設計得更加的高級和巧妙,使用了Pipeline模式。
爲了簡單瞭解,來個對比吧:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);
EventLoopGroup bossGroup = new NioEventLoopGroup(128);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
bossGroup 爲主線程,而 workerGroup 中的線程是 CPU 核心數乘以 2,因此對應的到 Reactor 線程模型中,我們知
1.2 再來了解一下NioEventLoop 和本地Selector的對應關係
NioEventLoop類型綁定了兩個重要的java本地類型:一個線程類型,一個Selector類型。本地Selector屬性的作用,用於註冊Java本地channel。本地線程屬性的作用,主要是用於輪詢。
1.3 NioEventLoopGroup與NioEventLoop的關係
下面這張圖其實是對Netty中NioEventLoop執行的流程圖:
- 從裏面可以知道,NioEventLoop就是在NioEventLoopGroup中創建的,一個NioEventLoopGroup可以包含多個NioEventLoop。
- 它會在Channel註冊完之後,調用run()開始工作。詳細的內容,下面介紹:
下面進入源碼分析:
2、NioEventLoop創建
從我們常寫的代碼入手:
public void start() {
init();
//Netty封裝了NIO,Reactor模型,Boss,worker
// Boss線程
EventLoopGroup bosGroup = new NioEventLoopGroup();
//worker工作線程
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//相當於Nio中的ServerSocketChannel
ServerBootstrap server = new ServerBootstrap();
//開始設置各種參數
server.group(bosGroup, workerGroup)
//主線程處理類
.channel(NioServerSocketChannel.class)
//子線程處理類Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel client) throws Exception {
//這裏採用了責任鏈模式進行
client.pipeline().addLast(new HttpResponseEncoder()) //編碼器
.addLast(new HttpRequestDecoder()) //解碼器
.addLast(new GPTomcatHandler()); //業務邏輯處理
}
})
.option(ChannelOption.SO_BACKLOG, 64) //針對主線程的配置,分配線程最大數量
.childOption(ChannelOption.SO_KEEPALIVE, true); //針對子線程的配置,保喫長連接
//啓動服務,綁定端口,異步
ChannelFuture future = server.bind(port).sync();
System.out.println("GP Tomcat 已啓動,監聽的端口是:" + port);
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
//關閉線程池
bosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
從new NioEventLoopGroup() 開始跟蹤,我們會依次得到下面的代碼:
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
//線程組默認線程數爲2倍的cpu數
//DEFAULT_EVENT_LOOP_THREADS=2*Runtime.getRuntime().availableProcessors()
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
#MultithreadEventExecutorGroup類,核心方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
//線程創建器,負責創建NioEventLoopGroup對應底層線程
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];//創建NioEventLoop對象數組
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//for循環創建每個NioEventLoop,調用newChild(),配置NioEventLoop核心參數
children[i] = newChild(executor, args);//newChild
success = true;
} 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 {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
//線程選擇器,給每個新連接分配NioEventLoop線程
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
#使用threadFactory創建線程
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
//每次執行任務創建一個線程,newDefaultThreadFactory定義了nioEventLoop-1-xx的線程名
threadFactory.newThread(command).start();
}
}
從上面我們可以看出創建NioEventLoopGroup的大致過程如下:
- 如果沒有傳入nThread,nThread 默認爲CPU核心數的兩倍
- 線程創建器,負責創建NioEventLoopGroup對應底層線程
- 創建NioEventLoop對象數組,並配置好其的核心參數
接下來,我們進入newChild()方法,也就是配置NioEventLoop核心參數,看看他到底幹了些啥事。
#NioEventLoopGroup
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);//父類構造函數
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
}
#SingleThreadEventExecutor類,父類構造函數
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ObjectUtil.checkNotNull(executor, "executor");
taskQueue = newTaskQueue(this.maxPendingTasks);//task隊列,外部線程將任務扔進隊列
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
//task queue
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
// This event loop never calls takeTask()
return PlatformDependent.newMpscQueue(maxPendingTasks);
}
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
if (DISABLE_KEYSET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();//用數組實現set
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
if (!(maybeSelectorImplClass instanceof Class) ||
// ensure the current selector implementation is what we can instrument.
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
}
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
//通過反射方式設置selectedKeySet
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);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
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;
}
}
});
if (maybeException instanceof Exception) {
selectedKeys = null;
Exception e = (Exception) maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
}
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
進行NioEventLoop參數初始化時,幹了很多事,但我們重點知道:
- 創建一個selector,並調用provider.openSelector()創建selector,輪詢初始化連接;
- 創建了一個線程阻塞隊列BlockIngqueue<Runable>
接下來我們在回到newChild()方法之後會調用的chooserFactory.newChooser(children);看看它又幹了什麼大事。
#DefaultEventExecutorChooserFactory 類
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();
private DefaultEventExecutorChooserFactory() { }
@SuppressWarnings("unchecked")
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
//判斷長度是否是2的冪,是則使用PowerOfTwoEventExecutorChooser,更高效
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];//&比取模高效,循環下標
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];//取模
}
}
}
上面的代碼邏輯主要表達的意思是:即如果nThreads 是2 的冪,則使用PowerOfTwoEventExecutorChooser,否則使用GenericEventExecutorChooser。這兩個Chooser 都重寫next()方法。next()方法的主要功能就是將數組索引循環位移,如下圖所示:
當索引移動最後一個位置時,再調用next()方法就會將索引位置重新指向0。
這個運算邏輯其實很簡單,就是每次讓索引自增後和數組長度取模:idx.getAndIncrement() % executors.length。但是就連一個非常簡單的數組索引運算,Netty 都幫我們做了優化。因爲在計算機底層,&與比%運算效率更高。
最後,來張時序圖總結一下:
3、NioEventLoop啓動
NioEventLoop的啓動此時是在Channel初始化,並在綁定端口之後開始的。具體可以回顧上篇博客:
Channel的創建:https://blog.csdn.net/qqq3117004957/article/details/106440866#Channel%E6%B3%A8%E5%86%8C
#AbstractBootstrap類doBind方法
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
#AbstractBootstrap類doBind0方法
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
//調用SingleThreadEventExecutor中execute方法
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
#SingleThreadEventExecutor中execute方法
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();//判斷是否當前eventloop中
if (inEventLoop) {
addTask(task);
} else {
startThread();//創建線程
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
public boolean inEventLoop() {
return inEventLoop(Thread.currentThread());
}
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
private void startThread() {
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
doStartThread();
}
}
}
//啓動線程
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();//保存當前線程,用於判斷
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();//觸發NioEventLoop執行
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = STATE_UPDATER.get(SingleThreadEventExecutor.this);
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
"before run() implementation terminates.");
}
try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
logger.warn(
"An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
NioEventLoop啓動流程步驟:
- bind->execute(task)[入口]:調用NioEventLoop的execute()方法執行綁定端口,操作封裝的Task
- startThread()->doStartThread()[創建線程]:非NioEventLoop線程調用startThread()方法,創建啓動線程
- ThreadPerTaskExecutor.execute():線程執行器執行任務,創建並啓動FastThreadLocalThread線程
- NioEventLoop.run()[啓動]
4、NioEventLoop執行過程
在上面doStartThread()方法中最終調用的是SingleThreadEventExecutor.this.run()方法,這個this 就是NioEventLoop 對象:
protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
} ......
}
}
終於看到似曾相識的代碼。上面代碼主要就是用一個死循環,在不斷地輪詢SelectionKey.select()方法,主要用來解決JDK 空輪詢Bug,而processSelectedKeys()就是針對不同的輪詢事件進行處理。如果客戶端有數據寫入,最終也會調用AbstractNioMessageChannel 的doReadMessages()方法。那麼我們先來看一下是哪裏調用的,通過追蹤processSelectedKeys()方法,最後會調用到NioEventLoop的processSelectedKey 方法:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
eventLoop = ch.eventLoop();
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
這裏可以看到熟悉的代碼,當有鏈接進來的時候,便會走 unsafe.read():這個裏面就調用了doReadMessages
- Connect, 即連接事件(TCP 連接), 對應於SelectionKey.OP_CONNECT.int值爲16.
- Accept, 即確認事件, 對應於SelectionKey.OP_ACCEPT.int值爲8.
- Read, 即讀事件, 對應於SelectionKey.OP_READ, 表示 buffer 可讀.int值爲1
- Write, 即寫事件, 對應於SelectionKey.OP_WRITE, 表示 buffer 可寫.int值爲4
總結一下:
- Netty 中Selector 事件輪詢是從EventLoop 的execute()方法開始的。
- 在EventLoop 的execute()方法中,會爲每一個任務創建一個獨立的線程,並保存到無鎖化串行任務隊列。
- 線程任務隊列的每個任務實際調用的是NioEventLoop 的run()方法。
- 在run 方法中調用processSelectedKeys()處理輪詢事件。
5、 Netty 解決JDK 空輪詢Bug
各位應該早有耳聞臭名昭著的Java NIO epoll 的bug,它會導致Selector 空輪詢,最終導致CPU 100%。官方聲稱在JDK1.6 版本的update18 修復了該問題,但是直到JDK1.7 版本該問題仍舊存在,只不過該BUG 發生概率降低了一些而已,它並沒有被根本解決。出現此Bug 是因爲當Selector 的輪詢結果爲空,也沒有wakeup 或新消息處理,則發生空輪詢,CPU 使用率達到100%。
在Netty 中最終的解決辦法是:創建一個新的Selector,將可用事件重新註冊到新的Selector 中來終止空輪訓。前面我們有提到select()方法解決了JDK 空輪訓的Bug,它到底是如何解決的呢?下面我們來一探究竟,進入select()方法的源碼:
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) { // .......int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
//.....
selectCnt = 1;
break;
}
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// timeoutMillis elapsed without anything selected.
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
}
從上面的代碼中可以看出,Selector 每一次輪詢都計數selectCnt++,開始輪詢會計時賦值給timeoutMillis,輪詢完成會計時賦值給time,這兩個時間差會有一個時間差,而這個時間差就是每次輪詢所消耗的時間。從上面的的邏輯看出,如果每次輪詢消耗的時間爲0,且重複次數超過512 次,則調用rebuildSelector()方法,即重構Selector。
繼續跟進源碼:
private void rebuildSelector0() {
final Selector oldSelector = selector;
final SelectorTuple newSelectorTuple;
newSelectorTuple = openSelector();
// Register all channels to the new Selector.
int nChannels = 0;
for (SelectionKey key: oldSelector.keys()) {
Object a = key.attachment();
try {
if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
continue;
}
int interestOps = key.interestOps();
key.cancel();
SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
}
在rebuildSelector()方法中,主要做了三件事情:
- 創建一個新的Selector。
- 將原來Selector 中註冊的事件全部取消。
- 將可用事件重新註冊到新的Selector 中,並激活。
6、Netty是如何實現異步串行無鎖化
在外部線程調用EventLoop或者channel的一些方法的時候,都會調用InEventLoop()方法檢查當前線程是否是NioEventloop中的線程,如如果是外部線程,就會將外部線程的的所有操作封裝成爲一個task,放進EventLoop的MPSCQ裏面,然後在NioEventLoop執行過程的第三個部分,這些task會被依次執行
以下從服務端bind()方法進入,進入dobind0()方法:
在外部線程執行Executer
private void startThread() {
// 其他代碼省略。。。。
this.doStartThread(); // -------------------------》在此方法中啓動線程
}
private void doStartThread() {
//這裏的executor就是之前的線程創建器:ThreadPerTaskExecutor,
//執行execute方法之後會創建一個FastLocalThread(netty包裝的Thread),並執行run();
this.executor.execute( new Runnable() { // -----------------------》進入execute方法源碼:
public void run() {
部分代碼省略。。。。
}
io.netty.util.concurrent.ingleThreadEventExecutor
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} else {
boolean inEventLoop = this.inEventLoop(); // -------------------->在此處判斷當前線程是不是EventLoop中的線程下面繼續跟源碼。
this.addTask(task); //將次任務將入任務隊列
if (!inEventLoop) {
this.startThread();
if (this.isShutdown()) {
boolean reject = false;
try {
if (this.removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException var5) {
;
}
if (reject) {
reject();
}
}
}
if (!this.addTaskWakesUp && this.wakesUpForTask(task)) {
this.wakeup(inEventLoop);
}
}
}
io.netty.util.concurrent.SingleThreadEventExecutor
public boolean inEventLoop(Thread thread) {
return thread == this.thread; // 返回的是當前線程是不是EvenetLoop中的線程
}
7、總結
說實話,自己總結了這麼長,看完都得花半個小時以上,但我認爲了解源碼並不是爲了記住它,而是從中瞭解Netty的工作機制,有一定的印象就爲成功。所以來個小總結:
- NioEventLoop就相當於NIO編程中的Reactor;
- 默認的一個NioEventLopGroup會創建CPU核心數的2倍線,即NioEventLoop的數量;
- NioEventLoop是在Channel註冊好之後,調用doBind0()方法開始的;
Netty空輪詢的解決方法:
netty 會在每次進行 selector.select(timeoutMillis) 之前記錄一下開始時間currentTimeNanos,在select之後記錄一下結束時間,判斷select操作是否至少持續了timeoutMillis秒(這裏將time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos改成time - currentTimeNanos >= TimeUnit.MILLISECONDS.toNanos(timeoutMillis)或許更好理解一些), 如果持續的時間大於等於timeoutMillis,說明就是一次有效的輪詢,重置selectCnt標誌,否則,表明該阻塞方法並沒有阻塞這麼長時間,可能觸發了jdk的空輪詢bug,當空輪詢的次數超過一個閥值的時候,默認是512,就開始重建selector。
netty是如何實現異步串行化的:
在外部線程調用EventLoop或者channel的一些方法的時候,都會調用InEventLoop()方法檢查當前線程是否是NioEventloop中的線程,如如果是外部線程,就會將外部線程的的所有操作封裝成爲一個task,放進EventLoop的MPSCQ裏面,然後在NioEventLoop執行過程的第三個部分,這些task會被依次執行。