ExecutorCompletionService使用


问题一:需要计算班级所有成绩总和,使用100个线程,每个线程计算一个学生成绩;
问题二:需要破解一个文件的密码,使用100个线程破解;

1、获取所有线程执行结果

对于问题一,需要获取所有线程执行结果,将每个线程的结果相加。

使用线程池如下:

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60L,
        TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());

1.1、使用线程池的invokeAll()

List<Future> invokeAl 1 (Col1ection<Cal 1 able > tasks )
传入任务集合,返回Future集合,遍历future.get()即可。
代码如下:

        List<Callable<String>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int index = i;
            list.add(() -> {
                try {
                    Random random = new Random();
                    int times = random.nextInt(10);
                    //模拟两次异常
                    if (index % 7 == 1) {
                        int tmp = index / 0;
                    }
                    TimeUnit.SECONDS.sleep(times);
                    return "子线程" + Thread.currentThread().getName() + " 耗费时长(s):" + times;
                } catch (Exception e) {
                    return "子线程:"+ Thread.currentThread().getName() + ": " + e.getMessage();
                }
            });
        }
        List<Future<String>> futures = executor.invokeAll(list);
        for (Future<String> future : futures) {
            System.out.println(future.get());
        }

结果如下:

子线程pool-1-thread-1 耗费时长(s)8
子线程:pool-1-thread-2: / by zero
子线程pool-1-thread-3 耗费时长(s)9
子线程pool-1-thread-4 耗费时长(s)4
子线程pool-1-thread-5 耗费时长(s)2
子线程pool-1-thread-6 耗费时长(s)2
子线程pool-1-thread-7 耗费时长(s)0
子线程pool-1-thread-8 耗费时长(s)8
子线程:pool-1-thread-9: / by zero
子线程pool-1-thread-10 耗费时长(s)6
主线程end-----
花费时间:9097

弊端:需要等待所有线程全部执行完成才能获取到结果。
适用于任务之间有关联,或必须等待最终结果,或者任务需要一次性提交运行的情况。

1.2 直接使用线程池的submit()

Future submit(Callable task)
每次传入一个任务,返回future执行结果。最后遍历future.get()
代码如下:

        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int index = i;
            Future<String> future = executor.submit(() -> {
                try {
                    Random random = new Random();
                    int times = random.nextInt(10);
                    //模拟两次异常
                    if (index % 7 == 1) {
                        int tmp = index / 0;
                    }
                    TimeUnit.SECONDS.sleep(times);
                    return "子线程" + Thread.currentThread().getName() + " 耗费时长(s):" + times;
                } catch (Exception e) {
                    return "子线程:" + Thread.currentThread().getName() + ": " + e.getMessage();
                }
            });
            futureList.add(future);
        }
        for (Future future : futureList) {
            System.out.println(future.get());
        }

结果如下:

子线程pool-1-thread-1 耗费时长(s)8
子线程:pool-1-thread-2: / by zero
子线程pool-1-thread-3 耗费时长(s)6
子线程pool-1-thread-4 耗费时长(s)4
子线程pool-1-thread-5 耗费时长(s)5
子线程pool-1-thread-6 耗费时长(s)4
子线程pool-1-thread-7 耗费时长(s)0
子线程pool-1-thread-8 耗费时长(s)4
子线程:pool-1-thread-9: / by zero
子线程pool-1-thread-10 耗费时长(s)7
主线程end-----
花费时间:8090

弊端:从头遍历Futures,当第一个线程执行时间很长时,后续线程执行结果也无法拿到。
适用于不需要批量提交的任务,可以一个一个提交

1.3 使用ExecutorCompletionService

ExecutorCompletionService service = new ExecutorCompletionServiceo(executor)
ExecutorCompletionService 构建一个计算完成的future队列

  • Future< V > submit( Runnable task , V result ):提交一个任务给底层的执行器。
  • Future< V > take( ): 移除一个已完成的结果, 如果没有任何已完成的结果可用则阻塞。
    使用submit提交任务,每当任务执行完毕将结果放入 future队列,使用take获取结果,实现实时获取结果。
    代码如下:
        ExecutorCompletionService<String> executorCompletionService = new ExecutorCompletionService<>(executor);
        for (int i = 0; i < 10; i++) {
            int index = i;
            executorCompletionService.submit(() -> {
                try {
                    Random random = new Random();
                    int times = random.nextInt(10);
                    //模拟两次异常
                    if (index % 7 == 1) {
                        int tmp = index / 0;
                    }
                    TimeUnit.SECONDS.sleep(times);
                    return "子线程" + Thread.currentThread().getName() + " 耗费时长(s):" + times;
                } catch (Exception e) {
                    return "子线程:" + Thread.currentThread().getName() + ": " + e.getMessage();
                }
            });
        }

        for (int i = 0; i < 10; i++) {
            System.out.println(executorCompletionService.take().get());
        }

结果如下:

子线程:pool-1-thread-2: / by zero
子线程:pool-1-thread-9: / by zero
子线程pool-1-thread-3 耗费时长(s)1
子线程pool-1-thread-7 耗费时长(s)2
子线程pool-1-thread-1 耗费时长(s)2
子线程pool-1-thread-6 耗费时长(s)5
子线程pool-1-thread-10 耗费时长(s)5
子线程pool-1-thread-4 耗费时长(s)6
子线程pool-1-thread-5 耗费时长(s)7
子线程pool-1-thread-8 耗费时长(s)9
主线程end-----
花费时间:9094

从结果可以看到获取到的线程结果不是按照任务的添加顺序,而是按照执行时间长短
适用于需要实时知道结果的情况。

2、获取多个线程的一个结果

对于问题二:需要破解一个文件的密码,使用100个线程破解;只需要获取最早的一个线程的执行结果即可,结算便可停止。
使用线程池如下:

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60L,
        TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());

使用线程池的invokeAny()
T invokeAny(Collection<Callable> tasks )
传入任务集合,返回其中一个执行结果
invokeAll的区别在于:
invokeAll:返回所有执行结果future集合
invokeAny:返回其中一个结果

代码如下:
        List<Callable<String>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int index = i;
            list.add(() -> {
                try {
                    Random random = new Random();
                    int times = random.nextInt(10) +2;
                    TimeUnit.SECONDS.sleep(times);
                    return "子线程" + Thread.currentThread().getName() + " 耗费时长(s):" + times;
                } catch (Exception e) {
                    return "子线程:"+ Thread.currentThread().getName() + ": " + e.getMessage();
                }
            });
        }
        System.out.println(executor.invokeAny(list));

结果如下:

子线程pool-1-thread-4 耗费时长(s)2
主线程end-----
花费时间:2106

如上结果所示,获取最早计算完成的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值