目录
1.addTaskWakesUp解释
SingleThreadEventExecutor 类中有一个成员变量 addTaskWakesUp 。网上的解释一般都是:这个参数的说明在调用addTask(Runnable)添加任务时是否唤醒线程。这种理解是错误的,真正的理解正好相反。调用addTask(Runnable)添加任务时是否能唤醒线程决定了addTaskWakesUp 是true还是false。
2.DefaultEventExecutor
DefaultEventExecutor 是 SingleThreadEventExecutor 的默认实现,按顺序执行提交的任务。
// ====DefaultEventExecutor.java========
@Override
protected void run() {
for (;;) {
// 循环取任务并执行
Runnable task = takeTask();
if (task != null) {
task.run();
updateLastExecutionTime();
}
if (confirmShutdown()) {
break;
}
}
}
run() 方法循环执行 takeTask() 从 taskQueue 取任务并执行。
//========SingleThreadEventExecutor.java========
// 从taskQueue中取下一个任务,如果当前没有则阻塞
// taskQueue必须是BlockingQueue类型,否则会抛UnsupportedOperationException异常
protected Runnable takeTask() {
assert inEventLoop();
if (!(taskQueue instanceof BlockingQueue)) {
throw new UnsupportedOperationException();
}
BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
for (;;) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
// scheduledTaskQueue中没有延时任务
if (scheduledTask == null) {
Runnable task = null;
try {
// 阻塞在这里,直到taskQueue中有新的任务
task = taskQueue.take();
if (task == WAKEUP_TASK) {
task = null;
}
} catch (InterruptedException e) {
// Ignore
}
return task;
} else {//scheduledTaskQueue中有延时任务
// 获取scheduledTask的到期时间
long delayNanos = scheduledTask.delayNanos();
Runnable task = null;
if (delayNanos > 0) {
try {
// 阻塞在这里,直到第一个延时任务时间到了或者有新的任务
task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
// Waken up.
return null;
}
}
// task == null说明poll的阻塞时间delayNanos到了,taskQueue中没有新的任务
if (task == null) {
// We need to fetch the scheduled tasks now as otherwise there may be a chance that
// scheduled tasks are never executed if there is always one task in the taskQueue.
// This is for example true for the read task of OIO Transport
// See https://github.com/netty/netty/issues/1614
// 阻塞时间到了,没有新的任务,
//需要用fetchFromScheduledTaskQueue方法将延时队列中到时间的任务放入taskQueue中
fetchFromScheduledTaskQueue();
task = taskQueue.poll();
}
if (task != null) {
return task;
}
}
}
}
在 taskQueue 阻塞的时候,如果添加新的任务,taskQueue会被唤醒,从而run方法可以继续执行。这样就实现了添加任务唤醒线程的作用。所以DefaultEventExecutor的构造方法中会传给父类addTaskWakesUp=true;
接下来看下DefaultEventExecutor实例:
@Test
public void execute() throws Exception {
final DefaultEventExecutor eventExecutors = new DefaultEventExecutor();
new Thread(() -> {
eventExecutors.execute(() -> System.out.println("start thread ===>>> " + Thread.currentThread().getName()));
}).start();
TimeUnit.SECONDS.sleep(1);
// 通过Execute方法添加任务
eventExecutors.execute(() -> {
System.out.println("task1 run with ===>>>" + Thread.currentThread().getName());
});
TimeUnit.SECONDS.sleep(30);
}
通过execute方法添加并执行任务,接下来看下Execute方法
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
// 添加任务,从上面的分析可以知道添加任务之后,阻塞会被唤醒,从而执行任务
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
// The task queue does not support removal so the best thing we can do is to just move on and
// hope we will be able to pick-up the task before its completely terminated.
// In worst case we will log on termination.
}
if (reject) {
reject();
}
}
}
// 由于任务在添加的时候,阻塞已被唤醒,这里就不需要主动wakeup(inEventLoop);
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
由于DefaultEventExecutor是通过BlockingQueue的阻塞来实现唤醒的,所以addTaskWakesUp=true。
因此,不需要执行wakeup(inEventLoop)主动唤醒。
3.NioEventLoop
对于NioEventLoop来时,阻塞的是selector.select()方法,该阻塞在添加任务时不能被唤醒,所以需要执行 wakeup(inEventLoop)主动唤醒。
所以,addTaskWakesUp=false。
4.总结
addTaskWakesUp 不是用来控制“添加任务时是否唤醒线程”的,而是根据类能实现的功能,来设置addTaskWakesUp的。
如果addTask(Runnable)添加任务时能唤醒线程,那么addTaskWakesUp=true;
如果addTask(Runnable)添加任务时不能唤醒线程,那么addTaskWakesUp=false;