线程执行者(六)运行多个任务并处理所有结果

简介:

运行多个任务并处理所有结果

执行者框架允许你在不用担心线程创建和执行的情况下,并发的执行任务。它还提供了Future类,这个类可以用来控制任务的状态,也可以用来获得执行者执行任务的结果。

如果你想要等待一个任务完成,你可以使用以下两种方法:

  • 如果任务执行完成,Future接口的isDone()方法将返回true。
  • ThreadPoolExecutor类的awaitTermination()方法使线程进入睡眠,直到每一个任务调用shutdown()方法之后完成执行。

这两种方法都有一些缺点。第一个方法,你只能控制一个任务的完成。第二个方法,你必须等待一个线程来关闭执行者,否则这个方法的调用立即返回。

ThreadPoolExecutor类提供一个方法,允许你提交任务列表给执行者,并且在这个列表上等待所有任务的完成。在这个指南中,你将学习如何使用这个特性,实现一个示例,执行3个任务,并且当它们完成时将结果打印出来。

准备工作…

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

如何做…

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

1.创建Result类,存储这个示例中并发任务产生的结果。


1 public class Result {

2.声明两个私有属性。一个String属性,名为name,另一个int属性,名为value。


1 private String name;
2 private int value;

3.实现相应的get()和set()方法,用来设置和获取name和value属性的值。


01 public String getName() {
02 return name;
03 }
04 public void setName(String name) {
05 this.name = name;
06 }
07 public int getValue() {
08  
09 return value;
10 }
11 public void setValue(int value) {
12 this.value = value;
13 }

4.创建Task类,实现Callable接口,参数化为Result类型。


1 public class Task implements Callable<Result> {

5.声明一个私有String属性,名为name。


1 private String name;

6.实现Task类构造器,初始化这个属性。


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

7.实现这个类的call()方法,在本例中,它将返回一个Result对象。


1 @Override
2 public Result call() throws Exception {

8.首先,写入一个信息到控制台,表明任务开始。


1 System.out.printf("%s: Staring\n",this.name);

9.然后,等待一个随机时间。


1 try {
2 long duration=(long)(Math.random()*10);
3 System.out.printf("%s: Waiting %d seconds for results.\n",this.name,duration);
4 TimeUnit.SECONDS.sleep(duration);
5 } catch (InterruptedException e) {
6 e.printStackTrace();
7 }

10.在Result对象中返回一个计算5个随机数的总和的int值。


1 int value=0;
2 for (int i=0; i<5; i++){
3 value+=(int)(Math.random()*100);
4 }

11.创建Result对象,用任务的名称和前面操作结果来初始化它。


1 Result result=new Result();
2 result.setName(this.name);
3 result.setValue(value);

12.写入一个信息到控制台,表明任务已经完成。


1 System.out.println(this.name+": Ends");

13.返回Result对象。


1 return result;
2 }

14.最后,实现这个示例的主类,创建Main类,实现main()方法。


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

15.使用Executors类的newCachedThreadPool()方法,创建ThreadPoolExecutor对象。


1 ExecutorService executor=(ExecutorService)Executors.newCachedThreadPool();

16.创建Task对象列表。创建3个Task对象并且用这个列表来存储。


1 List<Task> taskList=new ArrayList<>();
2 for (int i=0; i<3; i++){
3 Task task=new Task(i);
4 taskList.add(task);
5 }

17.创建Future对象列表,参数化为Result类型。


1 List<Future<Result>>resultList=null;

18.调用ThreadPoolExecutor类的invokeAll()方法。这个类将会返回之前创建的Future对象列表。


1 try {
2 resultList=executor.invokeAll(taskList);
3 } catch (InterruptedException e) {
4 e.printStackTrace();
5 }

19.使用shutdown()方法结束执行者。


1 executor.shutdown();

20.写入处理Future对象列表任务的结果。


01 System.out.println("Main: Printing the results");
02 for (int i=0; i<resultList.size(); i++){
03 Future<Result> future=resultList.get(i);
04 try {
05 Result result=future.get();
06 System.out.println(result.getName()+": "+result.
07 getValue());
08 } catch (InterruptedException | ExecutionException e) {
09 e.printStackTrace();
10 }
11 }

它是如何工作的…

在这个指南中,你已经学习了如何提交任务列表到执行者和使用invokeAll()方法等待它们的完成。这个方法接收Callable对象列表和返回 Future对象列表。这个列表将会有列表中每个任务的一个Future对象。Future对象列表的第一个对象是Callable对象列表控制的第一个任务,以此类推。

第一点要考虑的是,在存储结果对象的列表中声明的Future接口参数化的数据类型必须与使用的Callable对象的参数化相兼容。在本例中,你已经使用相同数据类型:Result类型。

另一个重要的一点就是关于invokeAll()方法,你会使用Future对象获取任务的结果。当所有的任务完成时,这个方法结束,如果你调用返回的Future对象的isDone()方法,所有调用都将返回true值。

不止这些…

ExecutorService类提供其他版本的invokeAll()方法:

  • invokeAll(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit unit):此方法执行所有任务,当它们全部完成且未超时,返回它们的执行结果。TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。

参见

  • 在第4章,线程执行者中的执行者执行返回结果的任务指南
  • 在第4章,线程执行者中的运行多个任务并处理第一个结果指南
目录
相关文章
|
1月前
|
消息中间件 前端开发 Java
美团面试:如何实现线程任务编排?
线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
28 1
|
1月前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
7天前
|
Java 程序员
Java多线程编程是指在一个进程中创建并运行多个线程,每个线程执行不同的任务,并行地工作,以达到提高效率的目的
【6月更文挑战第18天】Java多线程提升效率,通过synchronized关键字、Lock接口和原子变量实现同步互斥。synchronized控制共享资源访问,基于对象内置锁。Lock接口提供更灵活的锁管理,需手动解锁。原子变量类(如AtomicInteger)支持无锁的原子操作,减少性能影响。
18 3
|
5天前
|
Java
【技术解码】Java线程的五味人生:新建、就绪、运行、阻塞与死亡的哲学解读!
【6月更文挑战第19天】Java线程生命周期如同人生旅程,经历新建、就绪、运行、阻塞至死亡五阶段。从`new Thread()`的诞生到`start()`的蓄势待发,再到`run()`的全力以赴,线程在代码中奔跑。阻塞时面临挑战,等待资源释放,最终通过`join()`或中断结束生命。线程的每个状态转变,都是编程世界与哲思的交汇点。
|
3天前
|
Dart Serverless Android开发
Flutter 单线程模型保证UI运行流畅
Flutter 单线程模型保证UI运行流畅
9 0
|
1月前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印&quot;Hello World&quot;,每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
45 1
|
1月前
|
存储 安全 Java
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
|
1月前
|
监控 安全
线程死循环是多线程应用程序开发过程中一个难以忽视的问题,它源于线程在执行过程中因逻辑错误或不可预见的竞争状态而陷入永久运行的状态,严重影响系统的稳定性和资源利用率。那么,如何精准定位并妥善处理线程死循环现象,并在编码阶段就规避潜在风险呢?谈谈你的看法~
避免线程死循环的关键策略包括使用同步机制(如锁和信号量)、减少共享可变状态、设置超时、利用监控工具、定期代码审查和测试、异常处理及设计简洁线程逻辑。通过这些方法,可降低竞态条件、死锁风险,提升程序稳定性和可靠性。
36 0
|
3天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。
|
5天前
|
安全 Java
【极客档案】Java 线程:解锁生命周期的秘密,成为多线程世界的主宰者!
【6月更文挑战第19天】Java多线程编程中,掌握线程生命周期是关键。创建线程可通过继承`Thread`或实现`Runnable`,调用`start()`使线程进入就绪状态。利用`synchronized`保证线程安全,处理阻塞状态,注意资源管理,如使用线程池优化。通过实践与总结,成为多线程编程的专家。

相关实验场景

更多