线程执行者(八)执行者周期性地运行一个任务

简介:

声明:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González     译者:许巧辉     校对:方腾飞

执行者周期性地运行一个任务

执行者框架提供ThreadPoolExecutor类,使用池中的线程执行并发任务,从而避免所有线程的创建操作。当你提交任务给执行者,根据它的配置,它尽快地执行任务。当它结束,任务将被执行者删除,如果你想再次运行任务,你必须再次提交任务给执行者。

但是执行者框架通过ScheduledThreadPoolExecutor类可以执行周期性任务。在这个指南中,你将学习如何通过使用这个类的功能来安排一个周期性任务。

准备工作…

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建Task类,并指定它实现Runnable接口。

1 public class Task implements Runnable {

2.声明一个私有的、类型为String、名为name的属性,用来存储任务的名称。

1 private String name;

3.实现Task类的构造器,初始化name属性。

1 public Task(String name) {
2 this.name=name;
3 }

4.实现run()方法,写入实际日期到控制台,检查任务在指定的时间内执行。

1 @Override
2 public String call() throws Exception {
3 System.out.printf("%s: Starting at : %s\n",name,new Date());
4 return "Hello, world";
5 }

5.实现示例的主类,创建Main类,实现main()方法。

1 public class Main {
2 public static void main(String[] args) {

6.使用Executors类的newScheduledThreadPool()方法,创建ScheduledThreadPoolExecutor。传入参数1给这个方法。

1 ScheduledExecutorService executor=Executors.newScheduledThreadPool(1);

7.写入实际日期到控制台。

1 System.out.printf("Main: Starting at: %s\n",new Date());

8.创建一个新的Task对象。

1 Task task=new Task("Task");

9.使用scheduledAtFixRate()方法把它提交给执行者。使用前面创建的任务,数字1,数字2和常量TimeUnit.SECONDS作为参数。这个方法返回ScheduledFuture对象,它可以用来控制任务的状态。

1 ScheduledFuture<?> result=executor.scheduleAtFixedRate(task,12, TimeUnit.SECONDS);

10.创建10个循环步骤,写入任务下次执行的剩余时间。在循环中,使用ScheduledFuture对象的getDelay()方法,获取任务下次执行的毫秒数。

01 for (int i=0; i<10; i++){
02 System.out.printf("Main: Delay: %d\n",result.
03 getDelay(TimeUnit.MILLISECONDS));
04 //线程睡眠500毫秒
05 try {
06 TimeUnit.MILLISECONDS.sleep(500);
07 catch (InterruptedException e) {
08 e.printStackTrace();
09 }
10 }

11.使用shutdown()方法关闭执行者。

1 executor.shutdown();

12.使线程睡眠5秒,检查周期性任务是否完成。

1 try {
2 TimeUnit.SECONDS.sleep(5);
3 catch (InterruptedException e) {
4 e.printStackTrace();
5 }

13.写入一条信息到控制台,表明程序结束。

1 System.out.printf("Main: Finished at: %s\n",new Date());

它是如何工作的…

当你想要使用执行者框架执行一个周期性任务,你需要ScheduledExecutorService对象。Java建议使用 Executors类创建执行者,Executors类是一个执行者对象工厂。在本例中,你应该使用newScheduledThreadPool()方法,创建一个 ScheduledExecutorService对象。这个方法接收池的线程数作为参数。正如在本例中你只有一个任务,你传入了值1作为参数。

一旦你有执行者需要执行一个周期性任务,你提交任务给该执行者。你已经使用了scheduledAtFixedRate()方法。此方法接收4个参数:你想要周期性执行的任务、第一次执行任务的延迟时间、两次执行之间的间隔期间、第2、3个参数的时间单位。它是TimeUnit类的常 量,TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。

很重要的一点需要考虑的是两次执行之间的(间隔)期间,是这两个执行开始之间的一段时间。如果你有一个花5秒执行的周期性任务,而你给一段3秒时间,同一时刻,你将会有两个任务在执行。

scheduleAtFixedRate() 方法返回ScheduledFuture对象,它继承Future接口,这个方法和调度任务一起协同工作。ScheduledFuture是一个参数化接口(校对注:ScheduledFuture<V>)。在这个示例中,由于你的任务是非参数化的Runnable对象,你必须使用 问号作为参数。

你已经使用ScheduledFuture接口的一个方法。getDelay()方法返回直到任务的下次执行时间。这个方法接收一个TimeUnit常量,这是你想要接收结果的时间单位。

以下截图显示这个示例执行的输出:

6

你可以看出用Task:作为前缀的任务每2秒执行一次,并且每演示500毫秒向控制台写入一次。这就是main线程睡眠的时间。当你关闭执行者,这个计划任务结束它的执行,你将不会在控制台看到更多的信息。

不止这些…

ScheduledThreadPoolExecutor 提供其他方法来调度周期性任务。这就是scheduleWithFixedRate()方法。它与scheduledAtFixedRate()方法有一 样的参数,但它们之间的差异值得注意。在scheduledAtFixedRate()方法中,第3个参数决定两个执行开始的一段时间。在 scheduledWithFixedRate()方法中,参数决定任务执行结束与下次执行开始之间的一段时间。

当你使用 shutdown()方法时,你也可以通过参数配置一个SeduledThreadPoolExecutor的行为。shutdown()方法默认的行为是,当你调用这个方法时,计划任务就结束。 你可以使用ScheduledThreadPoolExecutor类的 setContinueExistingPeriodicTasksAfterShutdownPolicy()方法设置true值改变这个行为。在调用 shutdown()方法时,周期性任务将不会结束。

参见

  • 在第4章,线程执行者中的创建一个线程执行者食谱
  • 在第4章,线程执行者中的执行者延迟运行一个任务食谱
  • 文章转自 并发编程网-ifeve.com
目录
相关文章
|
3月前
|
编解码 网络协议 API
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
|
1月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
72 5
|
2月前
|
Java Spring
运行@Async注解的方法的线程池
自定义@Async注解线程池
143 3
|
3月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
67 1
|
3月前
|
存储 监控 Java
|
3月前
|
消息中间件 设计模式 安全
多线程魔法:揭秘一个JVM中如何同时运行多个消费者
【8月更文挑战第22天】在Java虚拟机(JVM)中探索多消费者模式,此模式解耦生产与消费过程,提升系统性能。通过`ExecutorService`和`BlockingQueue`构建含2个生产者及4个消费者的系统,实现实时消息处理。多消费者模式虽增强处理能力,但也引入线程安全与资源竞争等挑战,需谨慎设计以确保高效稳定运行。
90 2
|
4月前
|
Java Linux
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
|
3月前
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
|
3月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程