netty 之 GlobalEventExecutor
2021/8/21 6:08:38
本文主要是介绍netty 之 GlobalEventExecutor,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
GlobalEventExecutor 是AbstractScheduledEventExecutor 的实现,就是提供了一个单线程的单例,然后自动启动线程去执行任务,且如果空闲(即没有任务)超过1s则停止。 需要注意的是, 经测试, 这里的1s 是指总共耗时超过1s。 就是说,如果一个任务执行n久,然后执行完毕,然后0.2s 后又来了一个任务,然后这个0.2 是计算在内的,如果后面累计又停止0.8,那么加起来就是1s,那么就会终止线程,然后会重新创建一个新的线程。
这个做法感觉是非常奇怪的! 为什么会这样?它是怎么实现的呢?
首先看到 GlobalEventExecutor的关键属性有 quietPeriodTask , taskRunner, threadFactory,thread;
threadFactory仅仅用来创建 新的thread
thread 用来执行taskRunner
/** * Single-thread singleton {@link EventExecutor}. It starts the thread automatically and stops it when there is no * task pending in the task queue for 1 second. Please note it is not scalable to schedule large number of tasks to * this executor; use a dedicated executor. */ public final class GlobalEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor { private static final long SCHEDULE_QUIET_PERIOD_INTERVAL = TimeUnit.SECONDS.toNanos(1); public static final GlobalEventExecutor INSTANCE = new GlobalEventExecutor(); final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>(); final ScheduledFutureTask<Void> quietPeriodTask = new ScheduledFutureTask<Void>( this, Executors.<Void>callable(new Runnable() { @Override public void run() { // NOOP } }, null), ScheduledFutureTask.deadlineNanos(SCHEDULE_QUIET_PERIOD_INTERVAL), -SCHEDULE_QUIET_PERIOD_INTERVAL); // because the GlobalEventExecutor is a singleton, tasks submitted to it can come from arbitrary threads and this // can trigger the creation of a thread from arbitrary thread groups; for this reason, the thread factory must not // be sticky about its thread group // visible for testing final ThreadFactory threadFactory; // 仅仅用来创建 新的thread private final TaskRunner taskRunner = new TaskRunner(); private final AtomicBoolean started = new AtomicBoolean(); volatile Thread thread; private GlobalEventExecutor() { scheduledTaskQueue().add(quietPeriodTask); threadFactory = ThreadExecutorMap.apply(new DefaultThreadFactory( DefaultThreadFactory.toPoolName(getClass()), false, Thread.NORM_PRIORITY, null), this); } ... }
quietPeriodTask 当然也是Runnable,而且是ScheduledFutureTask,而且是很特殊的ScheduledFutureTask,因为它的period 参数是负数,这就意味着,它的deadlineNanos = nanoTime() - periodNanos; 就是它的deadline 时间是当前时间1s 之后。 因为ScheduledFutureTask 是使用优先级队列来实现的,那么它period 参数是负数意味着 quietPeriodTask 是被排在最末尾的。
观察源码 ,发现它其实就是专门用来计算 空闲时间的,每当正常提交的任务执行完了之后,它就开始执行,但是它 也不是真正的执行,他仅仅是等待;最多等待1s;如果等待的过程中有新的task任务,那么等待会被唤醒,这个是由 阻塞队列的性质决定的:
java.util.concurrent.ArrayBlockingQueue#offer(E)
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } }
private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); // 唤醒 }
TaskRunner 长什么样,TaskRunner 其实也是一个Runnable ,然后里面又是 先scheduledTaskQueue再 taskQueue 的顺序获取task,然后调用了task即Runnable 的run 方法, final class TaskRunner implements Runnable {
@Override public void run() { for (;;) { Runnable task = takeTask(); // 不管怎么样, 这里肯定都能获取一个task, 可能是正常提交的task, 如果没有,那么就是quietPeriodTask if (task != null) { // 一般情况下, 是不会返回null 的 try { task.run();// 这里让 task 执行完毕 } catch (Throwable t) { logger.warn("Unexpected exception from the global event executor: ", t); } if (task != quietPeriodTask) { continue;// 如果不是quietPeriodTask 那么说明还有其他正常提交的task, 不要执行后面的代码, 否则 通过后面的代码,检查 } } Queue<ScheduledFutureTask<?>> scheduledTaskQueue = GlobalEventExecutor.this.scheduledTaskQueue; // Terminate if there is no task in the queue (except the noop task). // 什么情况下,size 为1呢? 怎么会? 如果size 为1,那么 肯定就是quietPeriodTask 吧 if (taskQueue.isEmpty() && (scheduledTaskQueue == null || scheduledTaskQueue.size() == 1)) {// 如果定时调度任务queue里面为空,或者只有一个任务, // Mark the current thread as stopped. // The following CAS must always success and must be uncontended, // because only one thread should be running at the same time. boolean stopped = started.compareAndSet(true, false);// 走到这里,应该还只有一个线程在执行; 安全的设置started为false,表示正在停止 assert stopped;// 必须cas 成功 // Check if there are pending entries added by execute() or schedule*() while we do CAS above. // Do not check scheduledTaskQueue because it is not thread-safe and can only be mutated from a // TaskRunner actively running tasks. if (taskQueue.isEmpty()) { // A) No new task was added and thus there's nothing to handle // -> safe to terminate because there's nothing left to do // B) A new thread started and handled all the new tasks. // -> safe to terminate the new thread will take care the rest break;// 如果真的没有 正常提交的task了, 那么break,那么TaskRunner 也将会执行完毕,然后线程结束! } // There are pending tasks added again. if (!started.compareAndSet(false, true)) { // 走到这里, 说明上面的判断,即taskQueue.isEmpty 为false,那么还有任务需要执行,那么再次cas方式设置started为true,表示正在执行 // startThread() started a new thread and set 'started' to true. // -> terminate this thread so that the new thread reads from taskQueue exclusively. break; // 如果无法 cas方式设置started, 那么可能是其他其他线程已经启动,那么当前线程也无须再继续执行了! break 方式退出是唯一正常的操作! } // New tasks were added, but this worker was faster to set 'started' to true. // i.e. a new worker thread was not started by startThread(). // -> keep this thread alive to handle the newly added entries. } } } }
takeTask是关键
/** * Take the next {@link Runnable} from the task queue and so will block if no task is currently present. * * @return {@code null} if the executor thread has been interrupted or waken up. 一般情况下不返回null, 除非被中断,或者唤醒。 唤醒? */ Runnable takeTask() { BlockingQueue<Runnable> taskQueue = this.taskQueue; for (;;) { ScheduledFutureTask<?> scheduledTask = peekScheduledTask(); // 这里仅仅是 peek, why?因为 我们永远只从scheduledTaskQueue头部取,取出来后执行,执行完后 它会自动的自己重新放入queue, peek 其实是只有在 scheduledTaskQueue 为空的情况下才会返回 null, if (scheduledTask == null) { // 如果scheduledTaskQueue 都没有了数据, Runnable task = null; try { task = taskQueue.take();//那么从taskQueue中获取; 那么为什么需要先检查scheduledTaskQueue 是否有数据? } catch (InterruptedException e) { // Ignore } return task;// 这里,也有可能返回的是 quietPeriodTask } else { long delayNanos = scheduledTask.delayNanos(); // 这个是 scheduledTask 需要等待的时间 Runnable task = null; if (delayNanos > 0) { try { // 既然scheduledTask 还需要等待delayNanos , 那也不能白白等待, 不如在这个时间内趁机看看taskQueue 还有任务, 有任务,马上执行。 否则等待最多delayNanos; 超时了自然就是执行scheduledTask task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS); 如果scheduledTaskQueue 有定时任务,那么, poll 的方式等待它, 直到超时,或者 被唤醒; 这个做法其实有点奇怪。 // 这里的poll , 可能消耗的就是 quietPeriodTask 的时间 } catch (InterruptedException e) { // Waken up. return null; } } if (task == null) { 等待delayNanos;taskQueue 还是没有任务,超时了自然就是执行scheduledTask // 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(); // 如果还是为空,那么 scheduledTaskQueue的数据全部取出放到taskQueue;为什么全部?这样做是为了防止如果taskQueue 永远有任务,那么scheduledTaskQueue 没有机会执行 task = taskQueue.poll(); // 然后从taskQueue中获取 } if (task != null) { return task; // 这里, 如果没有其他task, 返回的肯定就是 quietPeriodTask } } } }
关于ScheduledFutureTask, 具体可以看看io.netty.util.concurrent.ScheduledFutureTask#run
这篇关于netty 之 GlobalEventExecutor的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01UniApp 中组件的生命周期是多少-icode9专业技术文章分享
- 2024-11-01如何使用Svg Sprite Icon简化网页图标管理
- 2024-10-31Excel数据导出课程:新手从入门到精通的实用教程
- 2024-10-31Excel数据导入课程:新手入门指南
- 2024-10-31RBAC的权限课程:新手入门教程
- 2024-10-31Svg Sprite Icon课程:新手入门必备指南
- 2024-10-31怎么配置 L2TP 允许多用户连接-icode9专业技术文章分享
- 2024-10-31怎么在FreeBSD上 安装 OpenResty-icode9专业技术文章分享
- 2024-10-31运行 modprobe l2tp_ppp 时收到“module not found”消息提醒是什么-icode9专业技术文章分享
- 2024-10-31FreeBSD的下载命令有哪些-icode9专业技术文章分享