|
| 1 | +调度任务执行 |
| 2 | +==== |
| 3 | + |
| 4 | +每隔一段时间需要调度任务执行,也许你想注册一个任务在客户端完成连接5分钟后执行,一个常见的用例是发送一个消息"你还活着?"到远端通,如果远端没有反应,则可以关闭通道(连接)和释放资源。 |
| 5 | + |
| 6 | +本节介绍使用强大的 EventLoop 实现任务调度,还会简单介绍 Java API的任务调度,以方便和 Netty 比较加深理解。 |
| 7 | + |
| 8 | +### 使用普通的 Java API 调度任务 |
| 9 | + |
| 10 | +在 Java 中使用 JDK 提供的 ScheduledExecutorService 实现任务调度。使用 Executors 提供的静态方法创建 ScheduledExecutorService,有如下方法 |
| 11 | + |
| 12 | +Table 15.1 java.util.concurrent.Executors-Static methods to create a ScheduledExecutorService |
| 13 | + |
| 14 | +方法 | 描述 |
| 15 | +----|---- |
| 16 | +newScheduledThreadPool(int corePoolSize) newScheduledThreadPool(int corePoolSize,ThreadFactorythreadFactory) | | 创建一个新的 |
| 17 | +ScheduledThreadExecutorService 用于调度命令来延迟或者周期性的执行。 corePoolSize 用于计算线程的数量 |
| 18 | +newSingleThreadScheduledExecutor() newSingleThreadScheduledExecutor(ThreadFact |
| 19 | +orythreadFactory) | 新建一个 ScheduledThreadExecutorService 可以用于调度命令来延迟或者周期性的执行。它将使用一个线程来执行调度的任务 |
| 20 | + |
| 21 | +下面的 ScheduledExecutorService 调度任务 60 执行一次 |
| 22 | + |
| 23 | +Listing 15.4 Schedule task with a ScheduledExecutorService |
| 24 | + |
| 25 | + ScheduledExecutorService executor = Executors |
| 26 | + .newScheduledThreadPool(10); //1 |
| 27 | + |
| 28 | + ScheduledFuture<?> future = executor.schedule( |
| 29 | + new Runnable() { //2 |
| 30 | + @Override |
| 31 | + public void run() { |
| 32 | + System.out.println("Now it is 60 seconds later"); //3 |
| 33 | + } |
| 34 | + }, 60, TimeUnit.SECONDS); //4 |
| 35 | + // do something |
| 36 | + // |
| 37 | + |
| 38 | + executor.shutdown(); //5 |
| 39 | + |
| 40 | +1. 新建 ScheduledExecutorService 使用10个线程 |
| 41 | +2. 新建 runnable 调度执行 |
| 42 | +3. 稍后运行 |
| 43 | +4. 调度任务60秒后执行 |
| 44 | +5. 关闭 ScheduledExecutorService 来释放任务完成的资源 |
| 45 | + |
| 46 | +### 使用 EventLoop 调度任务 |
| 47 | + |
| 48 | +使用 ScheduledExecutorService 工作的很好,但是有局限性,比如在一个额外的线程中执行任务。如果需要执行很多任务,资源使用就会很严重;对于像 Netty 这样的高性能的网络框架来说,严重的资源使用是不能接受的。Netty 对这个问题提供了很好的方法。 |
| 49 | + |
| 50 | +Netty 允许使用 EventLoop 调度任务分配到通道,如下面代码: |
| 51 | + |
| 52 | +Listing 15.5 Schedule task with EventLoop |
| 53 | + |
| 54 | + Channel ch = null; // Get reference to channel |
| 55 | + ScheduledFuture<?> future = ch.eventLoop().schedule( |
| 56 | + new Runnable() { |
| 57 | + @Override |
| 58 | + public void run() { |
| 59 | + System.out.println("Now its 60 seconds later"); |
| 60 | + } |
| 61 | + }, 60, TimeUnit.SECONDS); |
| 62 | + |
| 63 | +1. 新建 runnable 用于执行调度 |
| 64 | +2. 稍后执行 |
| 65 | +3. 调度任务60秒后运行 |
| 66 | + |
| 67 | +如果想任务每隔多少秒执行一次,看下面代码: |
| 68 | + |
| 69 | +Listing 15.6 Schedule a fixed task with the EventLoop |
| 70 | + |
| 71 | + Channel ch = null; // Get reference to channel |
| 72 | + ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate( |
| 73 | + new Runnable() { |
| 74 | + @Override |
| 75 | + public void run() { |
| 76 | + System.out.println("Run every 60 seconds"); |
| 77 | + } |
| 78 | + }, 60, 60, TimeUnit.SECONDS); |
| 79 | + |
| 80 | +1. 新建 runnable 用于执行调度 |
| 81 | +2. 将运行直到 ScheduledFuture 被取消 |
| 82 | +3. 调度任务60秒运行 |
| 83 | + |
| 84 | +取消操作,可以使用 ScheduledFuture 返回每个异步操作。 ScheduledFuture 提供一个方法用于取消一个调度了的任务或者检查它的状态。一个简单的取消操作如下: |
| 85 | + |
| 86 | + ScheduledFuture<?> future = ch.eventLoop() |
| 87 | + .scheduleAtFixedRate(..); //1 |
| 88 | + // Some other code that runs... |
| 89 | + future.cancel(false); //2 |
| 90 | + |
| 91 | +1. 调度任务并获取返回的 ScheduledFuture |
| 92 | +2. 取消任务,阻止它再次运行 |
| 93 | + |
| 94 | +### 调度的内部实现 |
| 95 | + |
| 96 | +Netty 内部实现其实是基于George Varghese 提出的 "Hashed and hierarchical timing wheels: Data structures to efficiently implement timer facility(散列和分层定时轮:数据结构有效实现定时器)"。这种实现只保证一个近似执行,也就是说任务的执行可能不是100%准确;在实践中,这已经被证明是一个可容忍的限制,不影响多数应用程序。所以,定时执行任务不可能100%准确的按时执行。 |
| 97 | + |
| 98 | +为了更好的理解它是如何工作,我们可以这样认为: |
| 99 | + |
| 100 | +* 在指定的延迟时间后调度任务; |
| 101 | +* 任务被插入到 EventLoop 的 Schedule-Task-Queue(调度任务队列); |
| 102 | +* 如果任务需要马上执行,EventLoop 检查每个运行; |
| 103 | +* 如果有一个任务要执行,EventLoop 将立刻执行它,并从队列中删除; |
| 104 | +* EventLoop 等待下一次运行,从第4步开始一遍又一遍的重复。 |
| 105 | + |
| 106 | +因为这样的实现计划执行不可能100%正确,对于多数用例不可能100%准备的执行计划任务;在 Netty 中,这样的工作几乎没有资源开销。 |
| 107 | + |
| 108 | +但是如果需要更准确的执行呢?很容易,你需要使用ScheduledExecutorService 的另一个实现,这不是 Netty 的内容。记住,如果不遵循 Netty 的线程模型协议,你将需要自己同步并发访问。 |
| 109 | + |
| 110 | + |
0 commit comments