当使用多种有效的并发任务解决问题时,会产生一个普遍的问题,那就是只需要第一个结果。例如,想要排列数组,可以使用各种各样的排列算法。也就是说,对指定的数组用最快的排序算法进行排序,然后从排好序的数组中找到第一个结果。
本节中,学习使用ThreadPoolExecutor类来实现这个场景。使用两种机制来尝试并确认一个用户。当其中一种机制能够确认的话,此用户则被认证通过。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
创建名为UserValidator的类,实现用户认证过程:
public class UserValidator {
-
定义名为name的私有String属性,用来存储用户认证系统的用户名:
private final String name;
-
实现类构造函数,初始化属性:
public UserValidator(String name) { this.name = name; }
-
实现validate()方法,接收两个String参数,分别是需要认证的用户名和用户密码:
public boolean validate(String name , String password) {
-
创建名为random的Random对象:
Random random = new Random();
-
随机等待一段时间来模拟用户认证过程:
try { long duration = (long) (Math.random() * 10); System.out.printf("Validator %s : Validating a user during %d seconds\n", this.name, duration); TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { return false; }
-
返回随机的Boolean值,当用户认证通过时,validate()方法返回true,否则false:
return random.nextBoolean(); }
-
实现getName()方法,返回名字的属性值:
public String getName() { return name; }
-
现在,创建名为ValidatorTask的类,使用UserValidation对象作为并发任务,来执行认证过程。指定其实现String类参数化的Callable接口:
public class ValidatorTask implements Callable<String>{
-
定义名为validator的私有UserValidator属性:
private final UserValidator validator;
-
定义两个私有String属性,分别是user和password:
private final String user; private final String password;
-
实现类构造函数,初始化这些属性:
public ValidatorTask(UserValidator validator, String user, String password) { this.validator = validator; this.user = user; this.password = password; }
-
实现call()方法,返回String对象:
@Override public String call() throws Exception {
-
如果用户未通过UserValidator对象认证,输出对应信息到控制台并且抛出Exception:
if(!validator.validate(user, password)) { System.out.printf("%s : The user has not been found\n", validator.getName()); throw new Exception("Error validating user"); }
-
否则,输出信息到控制台知名用户已认证通过,并返回UserUserValidator对象的名称:
System.out.printf("%s : The user has been found\n", validator.getName()); return validator.getName(); }
-
现在实现范例的主方法,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
创建两个名为user和password的String对象,初始化测试值:
String username = "test"; String password = "test";
-
创建两个UserValidator对象,名为ldapValidator和dbValidator:
UserValidator ldapValidator = new UserValidator("LDAP"); UserValidator dbValidator = new UserValidator("DataBase");
-
创建两个名为ldapTask和dbTask的TaskValidator对象,分别用ldapValidator和dbValidator初始化:
ValidatorTask ldapTask = new ValidatorTask(ldapValidator, username, password); ValidatorTask dbTask = new ValidatorTask(dbValidator, username, password);
-
创建TaskValidator对象列表,将已创建的两个对象添加进去:
List<ValidatorTask> taskList = new ArrayList<>(); taskList.add(ldapTask); taskList.add(dbTask);
-
使用Executors类的newCachedThreadPool()方法创建新的ThreadPoolExecutor对象,以及名为result的字符串变量:
ExecutorService executor = (ExecutorService)Executors.newCachedThreadPool(); String result;
-
调用执行器对象的invokeAny()方法,这个方法将taskList 作为参数接收且返回String。同样的,将返回的String对象输出到控制台:
try { result = executor.invokeAny(taskList); System.out.printf("Main : Result : %s\n", result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
-
使用shutdown()方法终止执行器,输出程序已结束的信息到控制台:
executor.shutdown(); System.out.printf("Main : End of the Execution\n");
工作原理
本范例的关键点在Main类中,ThreadPoolExecutor类的invokeAny()方法接受任务队列,然后加载任务队列,并且返回首个任务结果,不抛出异常。此方法返回与任务的call()方法返回的数据类型相同。这种情况下,返回String值。
下图显示本范例在控制台输出一个任务认证用户的执行信息:
范例包括两个UserValidator对象返回随机布尔型值,每个UserValidator对象被TaskValidator类实现的Callable对象使用。如果UserValidator类的validate()方法放回fasle值,TaskValidator类抛出异常,否则返回true。
所以,有两个任务能够返回true值或者抛出异常,出现以下四种可能:
- 两个任务均返回true值。invokeAny()方法的结果是在第一个完成的任务名称。
- 第一个任务返回true,第二个任务抛出Exception。invokeAny()方法的结果是第一个任务的名称。
- 第一个任务抛出Exception,第二个返回true值。nvokeAny()方法的结果是第二个任务的名称。
- 两个任务均抛出Exception。这种情况下,invokeAny()方法抛出ExecutionException异常。
如果多次运行此范例,四个可能的结果都可能得到。
下图显示当两个任务抛出异常时,本范例在控制台的输出信息:
扩展学习
ThreadPoolExecutor类提供了invokeAny()方法的另一种形式:
- invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) :如果未过指定时间,此方法执行所有的任务,且返回不抛出异常完成的第一个结果。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。
更多关注
- 本章“运行多任务并处理所有结果”小节。