任务调度
偶尔,你将需要调度一个任务以便稍后(延迟)执行或者周期性地执行。
例如,你可能想要注册一个在客户端已经连接了 5 分钟之后触发的任务。
一个常见的用例是,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该Channel 了。
在接下来的几节中,我们将展示如何使用核心的Java API 和Netty 的EventLoop 来调度任务。
然后,我们将研究 Netty 的内部实现,并讨论它的优点和局限性。
JDK 的任务调度API
在Java 5 之前,任务调度是建立在java.util.Timer 类之上的,其使用了一个后台Thread,并且具有与标准线程相同的限制。
随后,JDK 提供了java.util.concurrent 包,它定义了interface ScheduledExecutorService。
相关方法
- newScheduledThreadPool(int corePoolSize)/newScheduledThreadPool(int corePoolSize,ThreadFactorythreadFactory)
创建一个ScheduledThreadExecutorService,用于调度命令在指定延迟之后运行或者周期性地执行。
它使用corePoolSize 参数来计算线程数
- newSingleThreadScheduledExecutor()/newSingleThreadScheduledExecutor(ThreadFactorythreadFactory)
创建一个ScheduledThreadExecutorService,用于调度命令在指定延迟之后运行或者周期性地执行。它使用一个线程来执行被调度的任务
虽然选择不是很多但是这些预置的实现已经足以应对大多数的用例。
使用案例
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
...
executor.shutdown();
虽然ScheduledExecutorService API 是直截了当的,但是在高负载下它将带来性能上的负担。
在下一节中,我们将看到Netty 是如何以更高的效率提供相同的功能的。
个人疑问
什么负担?线程上下文的切换负担吗?
那 netty 又是怎么解决的呢?
使用EventLoop 调度任务
ScheduledExecutorService 的实现具有局限性。
例如,事实上作为线程池管理的一部分,将会有额外的线程创建。
如果有大量任务被紧凑地调度,那么这将成为一个瓶颈。
示例
Netty 通过Channel 的EventLoop 实现任务调度解决了这一问题,如代码清单7-3 所示。
- 7.3 使用 EventLoop 调度任务
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
经过60 秒之后,Runnable 实例将由分配给Channel 的EventLoop 执行。
如果要调度任务以每隔60 秒执行一次,请使用scheduleAtFixedRate()方法,如代码清单7-4 所示。
- 7.4 使用 EventLoop 调度周期性的任务
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("Run every 60 seconds");
}
}, 60, 60, TimeUnit.Seconds);
如我们前面所提到的,Netty的EventLoop扩展了ScheduledExecutorService(见图7-2),所以它提供了使用JDK实现可用的所有方法,包括在前面的示例中使用到的schedule()和scheduleAtFixedRate()方法。
所有操作的完整列表可以在ScheduledExecutorService的Javadoc中找到
- 7-5 使用ScheduledFuture 取消任务
要想取消或者检查(被调度任务的)执行状态,可以使用每个异步操作所返回的Scheduled-Future
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(...);
// Some other code that runs...
boolean mayInterruptIfRunning = false;
future.cancel(mayInterruptIfRunning);
这些例子说明,可以利用 Netty 的任务调度功能来获得性能上的提升。
反过来,这些也依赖于底层的线程模型,我们接下来将对其进行研究。
拓展阅读
个人收获
1、 理解最基础的知识才是最重要的。
比如:如何实现定时任务?自己实现的话。在底层方面?
2、思想是相通的,在一个地方需要的技术,在很多地方都是需要的。所以没必要学过多的看起来新颖的技术。
伟大始于渺小,前沿源于过往。
3、Future
在编程中和金融中都有这个词,感觉都挺有趣。
参考资料
《Netty in Action》 P117