1、Runnale和Callable

1.1、Runnale的缺陷

  • 无返回值
  • 不能跑出Checked Exception

方法已经规定了 run() 方法的返回类型是 void,而且这个方法没有声明抛出任何异常

Runnable为什么设计成这样(返回值为void 不抛出异常)?

即便往外抛也没有办法处理

1.2、Callable

callable有泛型的返回值,并且已经声明了 throws Exception

  1. public interface Callable<V> {
  2. V call() throws Exception;
  3. }

1.3、Callable 和 Runnable 的不同之处

最后总结一下 Callable 和 Runnable 的不同之处:

  • 方法名,Callable 规定的执行方法是 call(),而 Runnable 规定的执行方法是 run();
  • 返回值,Callable 的任务执行后有返回值,而 Runnable 的任务执行后是没有返回值的;
  • 抛出异常,call() 方法可抛出异常,而 run() 方法是不能抛出受检查异常的;
  • 和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 强大。

2、Future

2.1、简介

Future 类的 get 方法来获取Callable接口返回的执行结果,在call()未执行完毕之前,调用get的线程会被阻塞,直到call()方法返回了结果后,get会得到结果,且调用线程切换到runnable状态

Future 的 isDone 方法来判断任务是否已经执行完毕

Future是个存储器,存储了call()任务的结果,而这个任务的执行时间是无法提前确定的,完全取决于call()方法的执行情况

2.2、方法

  1. public interface Future<V> {
  2. boolean cancel(boolean mayInterruptIfRunning);
  3. boolean isCancelled();
  4. boolean isDone();
  5. V get() throws InterruptedException, ExecutionException;
  6. V get(long timeout, TimeUnit unit)
  7. throws InterruptedException, ExecutionException, TimeoutExceptio
  8. }

get() 方法

get 方法最主要的作用就是获取任务执行的结果,该方法在执行时的行为取决于 Callable 任务的状态,可能会发生以下 5 种情况。
(1)任务已经执行完毕,可以立刻返回,获取到任务执行的结果。
(2)任务还没有结果,在call()未执行完毕之前,调用get的线程会被阻塞,直到call()方法返回了结果后,get会得到结果,且调用线程切换到runnable状态
(3)任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException。
(4)任务被取消,如果任务被取消,我们用 get 方法去获取结果时则会抛出 CancellationException。
(5)任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。

cancel()方法

取消任务的执行

1、任务还没开始: 任务会被正常取消,未来也不会执行,方法返回true
2、任务已完成或者已取消:cancel方法执行失败,方法返回false
3、任务在执行中,根据cancel传入的mayInterruptIfRunning参数判断是否中断线程,true会中断线程,fasle,让线程运行完毕

Future.cancel(true)适用于:
1、任务能够处理中断

Future.cancel(false)适用于:
1、未能处理中断的任务
2、取消未开始的任务,但是等待已经开始执行的任务

isDone() 方法

判断任务是否已经执行完毕

isCancelled()方法

判断任务是否已经被取消

2.3、示例

2.3.1、多个Future获取结果

  1. public static void main(String[] args) {
  2. ExecutorService executorService = Executors.newFixedThreadPool(10);
  3. List<Future<Integer>> futures = new ArrayList<>();
  4. IntStream.range(0,10).forEach(e -> {
  5. Future<Integer> fulture = executorService.submit(() -> {
  6. Thread.sleep(3000);
  7. return new Random().nextInt();
  8. });
  9. futures.add(fulture);
  10. });
  11. futures.forEach(future -> {
  12. try {
  13. Integer integer = future.get();
  14. System.out.println(integer);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. } catch (ExecutionException e) {
  18. e.printStackTrace();
  19. }
  20. });
  21. }

2.3.2、超时情况

  1. public class FutureTimeOut {
  2. @Data
  3. @AllArgsConstructor
  4. static class Result{
  5. private String name;
  6. }
  7. public static void main(String[] args) {
  8. ExecutorService executorService = Executors.newFixedThreadPool(10);
  9. Future<Result> future = executorService.submit(() -> {
  10. try {
  11. Thread.sleep(4000);
  12. } catch (InterruptedException e) {
  13. System.out.println("线程被中断");
  14. }
  15. return new Result("正常返回结果");
  16. });
  17. Result result = null;
  18. try {
  19. result = future.get(3000, TimeUnit.MILLISECONDS);
  20. System.out.println(result.getName());
  21. } catch (InterruptedException e) {
  22. System.out.println("抛出InterruptedException");
  23. } catch (ExecutionException e) {
  24. System.out.println("抛出ExecutionException");
  25. } catch (TimeoutException e) {
  26. result = new Result("超时默认结果");
  27. System.out.println(result.getName());
  28. // true 如果线程还在运行 进行中断
  29. boolean cancel = future.cancel(true);
  30. System.out.println("cancel的结果:" + cancel);
  31. executorService.shutdown();
  32. }
  33. }
  34. }
  35. 输出
  36. 超时默认结果
  37. cancel的结果:true
  38. 线程被中断

3、FutureTask

3.1、简介

Futuretask是一种包装器,可以把Callable转换为Future和Runnable,同时实现两者都接口

3.2、示例

  1. public class FutureTaskDemo {
  2. public static void main(String[] args) {
  3. FutureTask<Integer> futureTask = new FutureTask(() -> {
  4. System.out.println("子线程正在运行");
  5. Thread.sleep(1000);
  6. return 10;
  7. });
  8. new Thread(futureTask).start();
  9. try {
  10. Integer result = futureTask.get();
  11. System.out.println("futureTask 运行结果" + result);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } catch (ExecutionException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }

4、Future的注意点

4.1、 当 for 循环批量获取 Future 的结果时容易 block

解决方案:

(1)get 方法调用时应使用 timeout 限制
(2)ExecutorCompletionService实现

4.1.1 ExecutorCompletionService

ExecutorCompletionService内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future

方法解析

  • Future submit(Callable task):提交一个Callable类型任务,并返回该任务执行结果关联的Future;
  • Future submit(Runnable task,V result):提交一个Runnable类型任务,并返回该任务执行结果关联的Future;
  • Future take():从内部阻塞队列中获取并移除第一个执行完成的任务,阻塞,直到有任务完成;
  • Future poll():从内部阻塞队列中获取并移除第一个执行完成的任务,获取不到则返回null,不阻塞;
  • Future poll(long timeout, TimeUnit unit):从内部阻塞队列中获取并移除第一个执行完成的任务,阻塞时间为timeout,获取不到则返回null;

示例

  1. public class ExecutorCompletionServiceDemo {
  2. public static void main(String[] args) {
  3. ExecutorService executorService = Executors.newFixedThreadPool(10);
  4. CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executorService);
  5. IntStream.range(0,10).forEach(e -> {
  6. completionService.submit(() -> {
  7. System.out.println(Thread.currentThread().getName() + "开始运行");
  8. Thread.sleep(new Double(Math.random() * 1000).longValue());
  9. System.out.println(Thread.currentThread().getName() + "运行完毕");
  10. return new Random().nextInt();
  11. });
  12. });
  13. IntStream.range(0,10).forEach(e -> {
  14. try {
  15. System.out.println(completionService.take().get());
  16. } catch (InterruptedException interruptedException) {
  17. interruptedException.printStackTrace();
  18. } catch (ExecutionException executionException) {
  19. executionException.printStackTrace();
  20. }
  21. });
  22. }
  23. }

输出

  1. pool-1-thread-1开始运行
  2. pool-1-thread-3开始运行
  3. pool-1-thread-4开始运行
  4. pool-1-thread-2开始运行
  5. pool-1-thread-6开始运行
  6. pool-1-thread-7开始运行
  7. pool-1-thread-5开始运行
  8. pool-1-thread-8开始运行
  9. pool-1-thread-10开始运行
  10. pool-1-thread-9开始运行
  11. pool-1-thread-8运行完毕
  12. -210530784
  13. pool-1-thread-3运行完毕
  14. 966014905
  15. pool-1-thread-4运行完毕
  16. 718336896
  17. pool-1-thread-1运行完毕
  18. -1688071909
  19. pool-1-thread-6运行完毕
  20. -1906296991
  21. pool-1-thread-10运行完毕
  22. 555840887
  23. pool-1-thread-2运行完毕
  24. 1109084760
  25. pool-1-thread-7运行完毕
  26. 537536106
  27. pool-1-thread-9运行完毕
  28. -70942598
  29. pool-1-thread-5运行完毕
  30. 362370045

4.2、Future 的生命周期不能后退

Future 的生命周期不能后退,一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来,也不能让一个已经完成计算的 Future 再次重新执行任务