定时且周期性的任务研究II--ScheduledThreadPoolExecutor

本文对比分析了Timer和ScheduledThreadPoolExecutor在任务执行效率、线程管理及异常处理上的优劣,并通过实例展示了如何利用ScheduledThreadPoolExecutor优化任务调度,避免线程泄漏,确保任务按预期顺序执行。

上一篇中我们看到了Timer的不足之处,本篇我们将围绕这些不足之处看看ScheduledThreadPoolExecutor是如何优化的。

为了研究方便我们需要两个类:

Java代码  收藏代码
  1. public class Task1 implements Callable<String> {  
  2.   
  3.     @Override  
  4.     public String call() throws Exception {  
  5.         String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
  6.         Random random = new Random();  
  7.         StringBuffer sb = new StringBuffer();  
  8.         for (int i = 0; i < 10; i++) {  
  9.             int number = random.nextInt(base.length());  
  10.             sb.append(base.charAt(number));  
  11.         }  
  12.         System.out.println("Task1 running: " + new Date());  
  13.         return sb.toString();  
  14.     }  
  15.   
  16. }  

 生成含有10个字符的字符串,使用Callable接口目的是我们不再任务中直接输出结果,而主动取获取任务的结果

Java代码  收藏代码
  1. public class LongTask implements Callable<String> {  
  2.   
  3.     @Override  
  4.     public String call() throws Exception {  
  5.         System.out.println("LongTask running: "+new Date());  
  6.         TimeUnit.SECONDS.sleep(10);  
  7.         return "success";  
  8.     }  
  9.   
  10. }  

长任务类,这里我们让任务沉睡10秒然后返回一个“success”

下面我们来分析一下ScheduledThreadPoolExecutor:

1、Timer中单线程问题是否在ScheduledThreadPoolExecutor中存在?

我们先来看一下面的程序:

Java代码  收藏代码
  1. public class ScheduledThreadPoolExec {  
  2.     public static void main(String[] args) throws InterruptedException,  
  3.             ExecutionException {  
  4.         <strong><span style="color: #ff0000;">ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(  
  5.                 2);</span>  
  6. </strong>  
  7.   
  8.   
  9.         ScheduledFuture future1 = executor.schedule(new Task1(), 5,  
  10.                 TimeUnit.SECONDS);  
  11.         ScheduledFuture future2 = executor.schedule(new LongTask(), 3,  
  12.                 TimeUnit.SECONDS);  
  13.   
  14.         BlockingQueue<ScheduledFuture> blockingQueue = new ArrayBlockingQueue<ScheduledFuture>(  
  15.                 2true);  
  16.         blockingQueue.add(future2);  
  17.         blockingQueue.add(future1);  
  18.   
  19.         System.out.println(new Date());  
  20.         while (!blockingQueue.isEmpty()) {  
  21.             ScheduledFuture future = blockingQueue.poll();  
  22.             if (!future.isDone())  
  23.                 blockingQueue.add(future);  
  24.             else  
  25.                 System.out.println(future.get());  
  26.         }  
  27.         System.out.println(new Date());  
  28.         executor.shutdown();  
  29.     }  
  30.   
  31. }  

 首先,我们定义了一个ScheduledThreadPoolExecutor它的池长度是2。接着提交了两个任务:第一个任务将延迟5秒执行,第二个任务将延迟3秒执行。我们建立了一个BlockingQueue,用它来存储了ScheduledFuture,使用ScheduledFuture可以获得任务的执行结果。在一个while循环中,我们每次将一个ScheduledFuture从队列中弹出,验证它是否被执行,如果没有被执行则再次将它加入队列中,如果被执行了,这使用ScheduledFuture的get方法获取任务执行的结果。看一下执行结果:

Java代码  收藏代码
  1. Thu Apr 21 19:23:02 CST 2011  
  2. LongTask running: Thu Apr 21 19:23:05 CST 2011  
  3. Task1 running: Thu Apr 21 19:23:07 CST 2011  
  4. h1o2wd942e  
  5. success  
  6. Thu Apr 21 19:23:15 CST 2011  

 我们看到长任务先运行,因为长任务只等待了3秒,然后是输出字符串的任务运行,两个任务开始时间相差2秒,而先输出了字符串而后才是长任务运行的结果success,最后我们查看一下整体的开始和结束时间相差了13秒。

这说明在ScheduledThreadPoolExecutor中它不是以一个线程运行任务的,而是以多个线程,如果用一个线程运行任务,那么长任务运行完之前是不会运行输出字符串任务的。其实这个“多个任务“是我们自己指定的注意一下标红的代码,如果我们把这行代码改为:

Java代码  收藏代码
  1. ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);  

 那么运行结果就会发生变化,

Java代码  收藏代码
  1. Thu Apr 21 19:36:56 CST 2011  
  2. LongTask running: Thu Apr 21 19:36:59 CST 2011  
  3. success  
  4. Task1 running: Thu Apr 21 19:37:09 CST 2011  
  5. y981iqd0or  
  6. Thu Apr 21 19:37:09 CST 2011  

 这时其实和使用Timer运行结果是一样的。任务是在一个线程里顺序执行的。

 

2、Timer中一但有运行时异常报出后续任务是否还会正常运行?

为了研究这个问题,我们还是需要一个能够抛出异常的任务,如下:

Java代码  收藏代码
  1. public class TimerExceptionTask extends TimerTask {  
  2.   
  3.     @Override  
  4.     public void run() {  
  5.         System.out.println("TimerExceptionTask: "+new Date());  
  6.         throw new RuntimeException();  
  7.     }  
  8.   
  9. }  

 我们对上面运行任务的代码做一点点小小的修改,先运行两个抛出异常的任务,如下:

Java代码  收藏代码
  1. public class ScheduledThreadPoolExec {  
  2.     public static void main(String[] args) throws InterruptedException,  
  3.             ExecutionException {  
  4.         ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(  
  5.                 2);  
  6.         ScheduledFuture future1 = executor.schedule(new Task1(), 5,  
  7.                 TimeUnit.SECONDS);  
  8.         ScheduledFuture future2 = executor.schedule(new LongTask(), 3,  
  9.                 TimeUnit.SECONDS);  
  10.         <strong><span style="color: #ff0000;">executor.schedule(new TimerExceptionTask(), 1, TimeUnit.SECONDS);  
  11.         executor.schedule(new TimerExceptionTask(), 2, TimeUnit.SECONDS);</span>  
  12. </strong>  
  13.   
  14.   
  15.   
  16.         BlockingQueue<ScheduledFuture> blockingQueue = new ArrayBlockingQueue<ScheduledFuture>(  
  17.                 2true);  
  18.         blockingQueue.add(future2);  
  19.         blockingQueue.add(future1);  
  20.   
  21.         System.out.println(new Date());  
  22.         while (!blockingQueue.isEmpty()) {  
  23.             ScheduledFuture future = blockingQueue.poll();  
  24.             if (!future.isDone())  
  25.                 blockingQueue.add(future);  
  26.             else  
  27.                 System.out.println(future.get());  
  28.         }  
  29.         System.out.println(new Date());  
  30.         executor.shutdown();  
  31.     }  
  32.   
  33. }  

 注意,标红的代码,如果这两个代码抛出错误后会影响后续任务,那么就应该在此终止,但是看一下结果,

Java代码  收藏代码
  1. Thu Apr 21 19:40:15 CST 2011  
  2. TimerExceptionTask: Thu Apr 21 19:40:16 CST 2011  
  3. TimerExceptionTask: Thu Apr 21 19:40:17 CST 2011  
  4. LongTask running: Thu Apr 21 19:40:18 CST 2011  
  5. Task1 running: Thu Apr 21 19:40:20 CST 2011  
  6. v5gcf01iiz  
  7. success  
  8. Thu Apr 21 19:40:28 CST 2011  

 后续任务仍然执行,可能会有朋友说:“你上面的池设置的是2,所以很有可能是那两个抛出异常的任务都在同一个线程中执行,而另一个线程执行了后续的任务”。那我们就把ScheduledThreadPoolExecutor设置成1看看,结果如下:

Java代码  收藏代码
  1. Thu Apr 21 19:43:00 CST 2011  
  2. TimerExceptionTask: Thu Apr 21 19:43:01 CST 2011  
  3. TimerExceptionTask: Thu Apr 21 19:43:02 CST 2011  
  4. LongTask running: Thu Apr 21 19:43:03 CST 2011  
  5. success  
  6. Task1 running: Thu Apr 21 19:43:13 CST 2011  
  7. 33kgv8onnd  
  8. Thu Apr 21 19:43:13 CST 2011  

后续任务也执行了,所以说ScheduledThreadPoolExecutor不会像Timer那样有线程泄漏现象。

对于周期性执行和Timer很类似这里就不再举例了。

L2J 是一个基于 Java 的开源项目,主要用于构建和运行类似于《天堂》(Lineage II)的大型多人在线游戏(MMORPG)。它提供了完整的服务器端实现,并支持与客户端进行交互。对于任务处理或开发相关的配置和实现方法,以下是详细的说明: ### 3.1 L2J 任务配置 在 L2J 中,任务通常指的是定时执行的操作,例如 NPC 行为、奖励发放、事件触发等。这些任务可以通过配置文件或代码逻辑进行定义。 #### 3.1.1 配置方式 L2J 使用 XML 文件或 Java 类来管理任务调度。常见的配置文件位于 `config` 目录下,例如 `gameserver.properties` 或自定义的 `scheduler.xml` 文件。 - **使用 Scheduler 配置任务** 可以通过 `ScheduledThreadPoolExecutor` 来安排周期性任务。以下是一个简单的 Java 示例: ```java import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class TaskScheduler { public static void main(String[] args) { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); Runnable task = () -> { System.out.println("执行每日奖励任务"); }; // 每天执行一次任务,初始延迟0秒 scheduler.scheduleAtFixedRate(task, 0, 24 * 60 * 60, TimeUnit.SECONDS); } } ``` 此代码片段可以嵌入到 L2J 的主服务中,用于定时触发任务[^3]。 #### 3.1.2 配置文件示例 在 `gameserver.properties` 中,可以设置任务调度器的行为参数: ```properties # 调度器线程池大小 SCHEDULER_THREADS=4 # 是否启用自动任务 ENABLE_AUTO_TASKS=true ``` 这些参数可以在启动时由配置加载类读取并应用。 --- ### 3.2 L2J 任务实现方法 L2J 中的任务实现主要依赖于 Java 的并发编程模型,尤其是 `java.util.concurrent` 包中的类。 #### 3.2.1 使用 Runnable 接口 任务可以通过实现 `Runnable` 接口来定义其行为,并通过线程池执行: ```java public class DailyLoginRewardTask implements Runnable { @Override public void run() { // 执行每日登录奖励逻辑 System.out.println("发放每日登录奖励"); } } ``` 然后将其提交给调度器: ```java ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.scheduleAtFixedRate(new DailyLoginRewardTask(), 0, 24, TimeUnit.HOURS); ``` #### 3.2.2 使用 Quartz 调度框架(可选) L2J 支持集成 Quartz 调度框架来实现更复杂的时间调度需求。Quartz 提供了基于 Cron 表达式的任务调度功能,适合需要灵活时间规则的任务。 添加 Quartz 依赖后,可以创建 Job 类: ```java import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobProvider; public class EventStartJob implements Job { @Override public void execute(JobExecutionContext context) { System.out.println("活动开始,触发特殊NPC生成"); } } ``` 并通过 Quartz 配置文件定义触发器: ```yaml jobs: - name: eventJob group: default trigger: cron: "0 0 12 * * ?" # 每天中午12点触发 job-class: com.l2j.EventStartJob ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值