1、Runnale和Callable
1.1、Runnale的缺陷
- 无返回值
- 不能跑出Checked Exception
方法已经规定了 run() 方法的返回类型是 void,而且这个方法没有声明抛出任何异常
Runnable为什么设计成这样(返回值为void 不抛出异常)?
即便往外抛也没有办法处理
1.2、Callable
callable有泛型的返回值,并且已经声明了 throws Exception
public interface Callable<V> {
V call() throws Exception;
}
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、方法
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutExceptio
}
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获取结果
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Integer>> futures = new ArrayList<>();
IntStream.range(0,10).forEach(e -> {
Future<Integer> fulture = executorService.submit(() -> {
Thread.sleep(3000);
return new Random().nextInt();
});
futures.add(fulture);
});
futures.forEach(future -> {
try {
Integer integer = future.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
2.3.2、超时情况
public class FutureTimeOut {
@Data
@AllArgsConstructor
static class Result{
private String name;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<Result> future = executorService.submit(() -> {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
return new Result("正常返回结果");
});
Result result = null;
try {
result = future.get(3000, TimeUnit.MILLISECONDS);
System.out.println(result.getName());
} catch (InterruptedException e) {
System.out.println("抛出InterruptedException");
} catch (ExecutionException e) {
System.out.println("抛出ExecutionException");
} catch (TimeoutException e) {
result = new Result("超时默认结果");
System.out.println(result.getName());
// true 如果线程还在运行 进行中断
boolean cancel = future.cancel(true);
System.out.println("cancel的结果:" + cancel);
executorService.shutdown();
}
}
}
输出
超时默认结果
cancel的结果:true
线程被中断
3、FutureTask
3.1、简介
Futuretask是一种包装器,可以把Callable转换为Future和Runnable,同时实现两者都接口
3.2、示例
public class FutureTaskDemo {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask(() -> {
System.out.println("子线程正在运行");
Thread.sleep(1000);
return 10;
});
new Thread(futureTask).start();
try {
Integer result = futureTask.get();
System.out.println("futureTask 运行结果" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
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;
示例
public class ExecutorCompletionServiceDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executorService);
IntStream.range(0,10).forEach(e -> {
completionService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "开始运行");
Thread.sleep(new Double(Math.random() * 1000).longValue());
System.out.println(Thread.currentThread().getName() + "运行完毕");
return new Random().nextInt();
});
});
IntStream.range(0,10).forEach(e -> {
try {
System.out.println(completionService.take().get());
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} catch (ExecutionException executionException) {
executionException.printStackTrace();
}
});
}
}
输出
pool-1-thread-1开始运行
pool-1-thread-3开始运行
pool-1-thread-4开始运行
pool-1-thread-2开始运行
pool-1-thread-6开始运行
pool-1-thread-7开始运行
pool-1-thread-5开始运行
pool-1-thread-8开始运行
pool-1-thread-10开始运行
pool-1-thread-9开始运行
pool-1-thread-8运行完毕
-210530784
pool-1-thread-3运行完毕
966014905
pool-1-thread-4运行完毕
718336896
pool-1-thread-1运行完毕
-1688071909
pool-1-thread-6运行完毕
-1906296991
pool-1-thread-10运行完毕
555840887
pool-1-thread-2运行完毕
1109084760
pool-1-thread-7运行完毕
537536106
pool-1-thread-9运行完毕
-70942598
pool-1-thread-5运行完毕
362370045
4.2、Future 的生命周期不能后退
Future 的生命周期不能后退,一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来,也不能让一个已经完成计算的 Future 再次重新执行任务