在Netty的执行线程中是没有用锁来保证同步的,并且它的执行效率非常高,那么,本文就先看一下Netty是如何设计EventLoop的,是如何执行工作的:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
从这行代码切入,先来看一下Netty的线程池的设计:
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
线程池的构造方法最终通过这个构造方法调用了父类的构造方法,这里面的参数有,线程的数量,执行器,selectorProvider, selectStrategyFactory, 和拒绝策略:
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
父类的构造方法首先会想我们传入的线程数量进行处理,如果我们传入的线程数量是0,那么就给他们赋值一个默认的线程的数目,这个数目是cpu核数的二倍,然后继续调用父类的构造方法:
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) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//创建一个EventLoop的数组
children = new EventExecutor[nThreads];
//创建每个线程
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//创建线程的具体实现
children[i] = newChild(executor, args);
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;
}
}
}
}
}
//创建一个线程选择器
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);
}
在这个构造函数里做了如下几件事:
1、判断传入的线程数目是否合法
2、是否创建了任务执行器,没有就先创建一个
3、创建线程
4、如果有线程创建失败,那么就将他们全部关闭
5、创建一个事件,监听线程是否创建完毕
接下来,我们看一下任务执行器excutor是如何创建的
new ThreadPerTaskExecutor(newDefaultThreadFactory());
首先它向构造方法中传入了一个默认的线程工厂作为参数:
protected ThreadFactory newDefaultThreadFactory() {
return new DefaultThreadFactory(getClass());
}
getClass是一个本地方法,得到的是当前运行的类的class对象,在这就是EventLoopGroup:
public DefaultThreadFactory(Class<?> poolType, boolean daemon, int priority) {
this(toPoolName(poolType), daemon, priority);
}
之后它又调用了这个构造方法,并且对我们传入的线程池的class对象做了处理,返回了线程池的名字:
public static String toPoolName(Class<?> poolType) {
//判断class对象是否为空
if (poolType == null) {
throw new NullPointerException("poolType");
}
//得到class对象的名字
String poolName = StringUtil.simpleClassName(poolType);
//根据名字的长短创建名字
switch (poolName.length()) {
case 0:
return "unknown";
case 1:
return poolName.toLowerCase(Locale.US);
default:
//这部就是返回了一个nioEventLoopGroup-poolId
if (Character.isUpperCase(poolName.charAt(0)) && Character.isLowerCase(poolName.charAt(1))) {
return Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1);
} else {
return poolName;
}
}
}
这个方法就是创建了一个线程池的名字,毕竟一个线程池会有一个任务执行器,所以要用名字对他们进行区分:
public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
if (poolName == null) {
throw new NullPointerException("poolName");
}
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
throw new IllegalArgumentException(
"priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
}
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
this.daemon = daemon;
this.priority = priority;
this.threadGroup = threadGroup;
}
最终调用的构造方法将我们传入的信息进行了保存,回到最初创建任务执行器的那个构造方法:
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
把刚才创建的线程工厂进行保存,接下来看一下eventLoop是如何被创建的:
children[i] = newChild(executor, args);
回到这行代码,进入newChild方法:
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对象,继续看它的构造方法:
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;
//通过provider创建一个选择器,之后的客户端channel都是要注册到这上面的
selector = openSelector();
selectStrategy = strategy;
}
这个方法对我们传入的参数进行了保存,并且继续调用父类的构造方法:
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);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
我们发现,原来一个eventLoop也是一个执行器,在这个构造方法面创建了一个任务队列,用来放当前eventLoop需要执行的任务:
chooser = chooserFactory.newChooser(children);
接下来看一下线程选择器的创建,现在又出来冒出来了一个chooserFactory,这个是从哪里来的呢?
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
我们来到构造方法,发现他是从DefaultEventExecutorChooserFactory这个工厂里获取的单例,我们走进去看一下chooser是怎么被创建的:
public EventExecutorChooser newChooser(EventExecutor[] executors) {
//根据能不能被优化选择创建不同的类
if (isPowerOfTwo(executors.length)) {
//线程数目是2的倍数
return new PowerOfTowEventExecutorChooser(executors);
} else {
//线程数目不是2的倍数
return new GenericEventExecutorChooser(executors);
}
}
Netty连最简单的轮询查找都做了优化,那么我们看一下优化和没优化有什么区别:
//PowerOfTowEventExecutorChooser
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
//GenericEventExecutorChooser
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
其实只是对next方法做了优化,如果线程数目是2的倍数,那么就可以用位运算来提高效率。
到这在创建线程池的构造方法就执行结束了,那么线程是创建好了,什么时候启动的呢?不知道大家还记不记得下面这个方法:
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// 让需要绑定端口的channel绑定的eventLoop来执行绑定的任务
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());
}
}
});
}
在进行绑定的时候就已经开始让线程去执行任务了,可是这个时候线程还没有启动,我们看一下Netty是如何操作的:
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//看下一下外部线程是不是和当前eventLoop绑定的线程是同一个,如果是就立即执行
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
//如果不是可能线程还没有启动
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
这个方法首先判断外部线程是不是和当前eventLoop绑定的线程是同一个,如果是就立即放到任务队列里面,如果不是那就是线程还没有启动,启动线程,再添加到任务队列里面:
private void startThread() {
//判断线程是否真的没有启动
if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
//通过cas操作将当前eventLoop的状态修改成激动状态
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
//启动eventLoop
doStartThread();
}
}
}
这个方法做了三件事
1、判断eventLoop是否未启动
2、通过CAS操作修改eventLoop的状态
3、启动eventLoop
我们仔细看一下eventLoop是如何启动的:
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 {
//使当前eventLoop启动
SingleThreadEventExecutor.this.run();
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.");
}
...
}
}
});
}
这个方法将启动eventLoop封装成了一个task交给线程池的执行器去执行,这个task的主要工作就是将eventLoop启动,我们先看一下线程池的执行器是如何执行任务的:
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
它调用了之前创建的threadFactory的newThread方法,接着又调用了start(),我们大致也能猜出来newThread返回了一个线程:
public Thread newThread(Runnable r) {
Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon()) {
if (!daemon) {
t.setDaemon(false);
}
} else {
if (daemon) {
t.setDaemon(true);
}
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
真正创建线程的是这个方法:
Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet());
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
原来,eventLoop的线程是一个FastThreadLocalThread,但是这个线程类是继承自Thread,只不过是对他做了优化,之后有时间我们看一下这个线程类是如何做优化的。OK,eventLoop里面工作的线程就创建好了,接下来就看他是如何被启动的:
SingleThreadEventExecutor.this.run();
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
//轮询获得io事件
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
// fallthrough
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
//执行IO事件
processSelectedKeys();
} finally {
// 执行任务队列的任务
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// Always handle shutdown even if the loop processing threw an exception.
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
这个方法里面有一个死循环,先是轮询获取io事件,然后根据执行io事件的时间和执行任务队列的时间是否是1:1,否则来决定如何处理这两种任务的衔接,我们先看一下如何轮询获得io事件的:
private void select(boolean oldWakenUp) throws IOException {
//得到当前eventLoop的选择器
Selector selector = this.selector;
try {
//记录空轮询的次数
int selectCnt = 0;
//得到开始轮询的时间
long currentTimeNanos = System.nanoTime();
//获得轮询的截至时间
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
//如果已经到截至时间了,进行一次非阻塞的轮询,返回
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
// 如果任务队列里面有任务并且成功的将select唤醒,进行一次非阻塞select返回
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
//设置过期时间进行一次阻塞轮询
int selectedKeys = selector.select(timeoutMillis);
//空轮询次数+1
selectCnt ++;
//如果有io事件,或者线程设置的状态为唤醒状态,或者当前状态处于唤醒或者任务队列里面有任务,或者定时任务队列里面有任务,则退出
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
// As this is most likely a bug in the handler of the user or it's client library we will
// also log it.
//
// See https://github.com/netty/netty/issues/2426
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// 如果已经超过设置的轮询时间了,重置空轮询次数
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
//如果在截至时间内空轮询的次数超过512次,就要重建一个selector,为了防止cpu100%的jdkbug
rebuildSelector();
selector = this.selector;
// 进行一次非阻塞轮询
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
}
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
// Harmless exception - log anyway
}
}
这个方法的主要做的工作在注释里都说明了,我们着重来看一下是如何来重建selector的:
public void rebuildSelector() {
//如果当前外部线程不是当前eventLoop绑定的外部线程,那么把这项工作封装成一个task放到任务队列里面
if (!inEventLoop()) {
execute(new Runnable() {
@Override
public void run() {
rebuildSelector();
}
});
return;
}
//保存旧的selector
final Selector oldSelector = selector;
final Selector newSelector;
if (oldSelector == null) {
return;
}
try {
//创建一个新的selector
newSelector = openSelector();
} catch (Exception e) {
logger.warn("Failed to create a new Selector.", e);
return;
}
// 将原本注册到旧的selector上的channel注册到新的selector上
int nChannels = 0;
for (;;) {
try {
//遍历oldSelector上所有的key
for (SelectionKey key: oldSelector.keys()) {
//获得当前遍历道德channel
Object a = key.attachment();
try {
if (!key.isValid() || key.channel().keyFor(newSelector) != null) {
continue;
}
//获得当前channel感兴趣的事
int interestOps = key.interestOps();
key.cancel();
//注册到新的selector上
SelectionKey newKey = key.channel().register(newSelector, interestOps, a);
if (a instanceof AbstractNioChannel) {
// Update SelectionKey
((AbstractNioChannel) a).selectionKey = newKey;
}
nChannels ++;
} catch (Exception e) {
logger.warn("Failed to re-register a Channel to the new Selector.", e);
if (a instanceof AbstractNioChannel) {
AbstractNioChannel ch = (AbstractNioChannel) a;
ch.unsafe().close(ch.unsafe().voidPromise());
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
invokeChannelUnregistered(task, key, e);
}
}
}
} catch (ConcurrentModificationException e) {
// Probably due to concurrent modification of the key set.
continue;
}
break;
}
selector = newSelector;
try {
// time to close the old selector as everything else is registered to the new one
oldSelector.close();
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to close the old Selector.", t);
}
}
logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
}
就是先创建了一个新的selector,然后通过循环把所有的注册到旧的selector上的channel注册到新的selector上。
接下来我们看一下Netty是如何处理io事件的:
private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
for (int i = 0;; i ++) {
//获得当前的attachment,也就是channel
final SelectionKey k = selectedKeys[i];
if (k == null) {
break;
}
// 这个位置处理完了要置为空
selectedKeys[i] = null;
//获得channel
final Object a = k.attachment();
//真正执行io事件
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
for (;;) {
i++;
if (selectedKeys[i] == null) {
break;
}
selectedKeys[i] = null;
}
//再次轮询io事件
selectAgain();
// 重新过的selectedKey
selectedKeys = this.selectedKeys.flip();
i = -1;
}
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//获得unsafe类
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
//获得当前channel绑定的eventLoop
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
//获得事件
int readyOps = k.readyOps();
// 根据不同的事件进入不同的分支进行处理
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
处理完io事件时候,就要处理任务队列里面的方法:
protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false;
do {
//聚类任务队列和定时任务队列
fetchedAll = fetchFromScheduledTaskQueue();
//执行任务队列的方法
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
if (ranAtLeastOne) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
afterRunningAllTasks();
return ranAtLeastOne;
}
这个方法主要做了两件事:
1、将定时任务和普通任务合并
2、执行任务
private boolean fetchFromScheduledTaskQueue() {
//获得执行任务的截至时间
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//根据截至时间从任务队列中一个一个的取出在截至时间到来之前要执行的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
//将任务放到普通任务队列中
if (!taskQueue.offer(scheduledTask)) {
// 如果放入失败,那么就要将它重新返回定是队列里面
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
这个方法首先会得到一个截至时间,然后根据这个截至时间到定时任务队列里面一个一个的拿到在截至时间之内要被执行的任务放到普通任务队列里面。
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
//获得任务队列里面的一个任务
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
//开始执行
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
}
}
}
protected static void safeExecute(Runnable task) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception. Task: {}", task, t);
}
}
这个方法通过循环一个一个的执行。
这样eventLoop的执行流程就介绍完了,还有一个定时任务队列之前没提到过,我们一起来看一下
Queue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) {
scheduledTaskQueue = new PriorityQueue<ScheduledFutureTask<?>>();
}
return scheduledTaskQueue;
}
我们发现了有这样一个方法,里面返回定时任务队列,再返回之前进行非空判断如果为空就创建一个定时任务队列,怪不得我们之前我们没提到过,原来是用到的时候才会创建。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
ObjectUtil.checkNotNull(command, "command");
ObjectUtil.checkNotNull(unit, "unit");
if (delay < 0) {
throw new IllegalArgumentException(
String.format("delay: %d (expected: >= 0)", delay));
}
return schedule(new ScheduledFutureTask<Void>(
this, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay))));
}
这是一个公共方法,用来想定时任务队列里面添加一个定时任务
<V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (inEventLoop()) {
scheduledTaskQueue().add(task);
} else {
execute(new Runnable() {
@Override
public void run() {
scheduledTaskQueue().add(task);
}
});
}
return task;
}
如果当前的外部线程是当前eventLoop绑定的线程,那么就直接添加进去,如果不是,把它封装成一个普通的task放到任务队列里面等待串行执行。
好了这节我们就介绍结束了,在下一篇文章中我们将介绍客户端channel加入连接的处理。