线程执行者(五)运行多个任务并处理第一个结果

简介:

运行多个任务并处理第一个结果

在并发编程中的一个常见的问题就是,当有多种并发任务解决一个问题时,你只对这些任务的第一个结果感兴趣。比如,你想要排序一个数组。你有多种排序算法。 你可以全部启用它们,并且获取第一个结果(对于给定数组排序最快的算法的结果)。

在这个指南中,你将学习如何使用ThreadPoolExecutor类的场景。你将继续实现一个示例,一个用户可以被两种机制验证。如果使用其中一个机制验证通过,用户将被确认验证通过。

准备工作…

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

如何做…

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

1.创建UserValidator类,实现用户验证过程。


1 public class UserValidator {

2.声明一个私有的、类型为String、名为name的属性,用来存储系统验证用户的名称。


1 private String name;

3.实现UserValidator类的构造器,初始化这个属性。


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

4.实现validate()方法。接收你想要验证用户的两个String类型参数,一个为name,一个为password。


1 public boolean validate(String name, String password) {

5.创建Random对象,名为random。


1 Random random=new Random();

6.等待个随机时间,用来模拟用户验证的过程。


1 try {
2 long duration=(long)(Math.random()*10);
3 System.out.printf("Validator %s: Validating a user during %d seconds\n",this.name,duration);
4 TimeUnit.SECONDS.sleep(duration);
5 } catch (InterruptedException e) {
6 return false;
7 }

7.返回一个随机Boolean值。如果用户验证通过,这个方法将返回true,否则,返回false。


1 return random.nextBoolean();
2 }

8.实现getName()方法,返回name属性值。


1 public String getName(){
2 return name;
3 }

9.现在,创建TaskValidator类,用来执行UserValidation对象作为并发任务的验证过程。指定它实现Callable接口,并参数化为String类型。


1 public class TaskValidator implements Callable<String> {

10.声明一个私有的、类型为UserValidator、名为validator的属性。


1 private UserValidator validator;

11.声明两个私有的、类型为String、名分别为user和password的属性。


1 private String user;
2 private String password;

12.实现TaskValidator类,初始化这些属性。


1 public TaskValidator(UserValidator validator, String user,
2 String password){
3 this.validator=validator;
4 this.user=user;
5 this.password=password;
6 }

13.实现call()方法,返回一个String类型对象。


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

14.如果用户没有通过UserValidator对象验证,写入一条信息到控制台,表明这种情况,并且抛出一个Exception异常。


1 if (!validator.validate(user, password)) {
2 System.out.printf("%s: The user has not been found\n",validator.getName());
3 throw new Exception("Error validating user");
4 }

15.否则,写入一条信息到控制台表明用户已通过验证,并返回UserValidator对象的名称。


1 System.out.printf("%s: The user has been found\n",validator.getName());
2 return validator.getName();

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


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

17.创建两个String对象,一个名为name,另一个名为password,使用”test”值初始化它们。


1 String username="test";
2 String password="test";

18.创建两个UserValidator对象,一个名为ldapValidator,另一个名为dbValidator。


1 UserValidator ldapValidator=new UserValidator("LDAP");
2 UserValidator dbValidator=new UserValidator("DataBase");

19.创建两个TaskValidator对象,分别为ldapTask和dbTask。分别使用ldapValidator和dbValidator初始化它们。


1 TaskValidator ldapTask=new TaskValidator(ldapValidator,username, password);
2 TaskValidator dbTask=new TaskValidator(dbValidator,username,password);

20.创建TaskValidator队列,添加两个已创建的对象(ldapTask和dbTask)。


1 List<TaskValidator> taskList=new ArrayList<>();
2 taskList.add(ldapTask);
3 taskList.add(dbTask);

21.使用Executors类的newCachedThreadPool()方法创建一个新的ThreadPoolExecutor对象和一个类型为String,名为result的变量。


1 ExecutorService executor=(ExecutorService)Executors.newCachedThreadPool();
2 String result;

22.调用executor对象的invokeAny()方法。该方法接收taskList参数,返回String类型。同样,它将该方法返回的String对象写入到控制台。


1 try {
2 result = executor.invokeAny(taskList);
3 System.out.printf("Main: Result: %s\n",result);
4 } catch (InterruptedException e) {
5 e.printStackTrace();
6 } catch (ExecutionException e) {
7 e.printStackTrace();
8 }

23.使用shutdown()方法结束执行者,写入一条信息到控制台,表明程序已结束。


1 executor.shutdown();
2 System.out.printf("Main: End of the Execution\n");

它是如何工作的…

Main 类是这个示例的关键。ThreadPoolExecutor类中的invokeAny()方法接收任务数列,并启动它们,返回完成时没有抛出异常的第一个 任务的结果。该方法返回的数据类型与启动任务的call()方法返回的类型一样。在本例中,它返回String值。

以下截图显示,当一个任务验证用户时,执行示例的部分输出:

3

这 个示例有两个返回随机Boolean值的UserValidator对象。每个UserValidator对象被一个实现TaskValidator类的Callable对象使用。如果UserValidator类的validate()方法返回false,TaskValidator类将抛出异常。否则,它将返回true值。

所以,我们有两个任务,可以返回true值或抛出异常。有以下4种情况:

  • 两个任务都返回ture。invokeAny()方法的结果是第一个完成任务的名称。
  • 第一个任务返回true,第二个任务抛出异常。invokeAny()方法的结果是第一个任务的名称。
  • 第一个任务抛出异常,第二个任务返回true。invokeAny()方法的结果是第二个任务的名称。
  • 两个任务都抛出异常。在本例中,invokeAny()方法抛出一个ExecutionException异常。

如果你多次运行这个示例,你可以获取以上这4种情况。

以下截图显示当两个任务抛出异常时,应用程序的输出:

4

不止这些…

ThreadPoolExecutor类提供其他版本的invokeAny()方法:

  • invokeAny(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit unit):此方法执行所有任务,并返回第一个完成(未超时)且没有抛出异常的任务的结果。TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS,MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。

参见

  • 在第4章,线程执行者中的运行多个任务并处理所有结果指南
目录
相关文章
|
1月前
|
消息中间件 前端开发 Java
美团面试:如何实现线程任务编排?
线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
27 1
|
1月前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
1月前
|
存储 算法 Java
【C/C++ 线程池设计思路】 深入探索线程池设计:任务历史记录的高效管理策略
【C/C++ 线程池设计思路】 深入探索线程池设计:任务历史记录的高效管理策略
99 0
|
1天前
|
Java 程序员
Java多线程编程是指在一个进程中创建并运行多个线程,每个线程执行不同的任务,并行地工作,以达到提高效率的目的
【6月更文挑战第18天】Java多线程提升效率,通过synchronized关键字、Lock接口和原子变量实现同步互斥。synchronized控制共享资源访问,基于对象内置锁。Lock接口提供更灵活的锁管理,需手动解锁。原子变量类(如AtomicInteger)支持无锁的原子操作,减少性能影响。
16 3
|
1月前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印&quot;Hello World&quot;,每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
44 1
|
1月前
|
存储 安全 Java
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
|
1月前
|
监控 安全
线程死循环是多线程应用程序开发过程中一个难以忽视的问题,它源于线程在执行过程中因逻辑错误或不可预见的竞争状态而陷入永久运行的状态,严重影响系统的稳定性和资源利用率。那么,如何精准定位并妥善处理线程死循环现象,并在编码阶段就规避潜在风险呢?谈谈你的看法~
避免线程死循环的关键策略包括使用同步机制(如锁和信号量)、减少共享可变状态、设置超时、利用监控工具、定期代码审查和测试、异常处理及设计简洁线程逻辑。通过这些方法,可降低竞态条件、死锁风险,提升程序稳定性和可靠性。
33 0
|
1月前
|
Java Spring
定时任务里面的任务多线程操作
该内容是关于Spring Boot中配置异步任务和定时任务的代码示例。首先通过`@Configuration`和`@EnableAsync`开启异步支持,然后定义线程池,如使用`ThreadPoolExecutor`并设置核心线程数、最大线程数等参数。接着,在需要异步执行的方法上添加`@Async`注解。此外,通过`@EnableScheduling`开启定时任务,并使用`@Scheduled`定义具体任务和执行周期。若需指定多个线程池,可以创建不同的`Executor` bean,并在`@Async`中指定线程池名称。
27 2
|
6天前
|
Java API
详细探究Java多线程的线程状态变化
Java多线程的线程状态主要有六种:新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)和终止(TERMINATED)。线程创建后处于NEW状态,调用start()后进入RUNNABLE状态,表示准备好运行。当线程获得CPU资源,开始执行run()方法时,它处于运行状态。线程可以因等待锁或调用sleep()等方法进入BLOCKED或等待状态。线程完成任务或发生异常后,会进入TERMINATED状态。
|
6天前
|
存储 安全 Java
Java多线程中线程安全问题
Java多线程中的线程安全问题主要涉及多线程环境下对共享资源的访问可能导致的数据损坏或不一致。线程安全的核心在于确保在多线程调度顺序不确定的情况下,代码的执行结果始终正确。常见原因包括线程调度随机性、共享数据修改以及原子性问题。解决线程安全问题通常需要采用同步机制,如使用synchronized关键字或Lock接口,以确保同一时间只有一个线程能够访问特定资源,从而保持数据的一致性和正确性。

相关实验场景

更多