文章目录
1 Future和Callback
简单来说,Future代表着一个异步任务在未来的执行结果,这个结果可以在最终的某个时间节点通过Future的get方法来获得,关于Future更多的细节和原理,可参考多线程设计模式:Future设计模式
对于长时间运行的任务来说,使其异步执行并立即返回一个Future接口是一种比较不错的选择,因为这样可以允许程序在等待结果的同时继续去执行其他的任务
Future接口也是在JDK1.5版本中随着并发包一起被引入JDK的,Future接口的定义如下所示
public interface Future<V> {
/**
* 取消任务的执行,
* 如果mayInterruptIfRunning为true,则工作线程将会被中断,否则即使执行了cancel方法,也会等待其完成,
* 无论mayInterruptIfRunning为true还是false,isCancelled()都会为true,并且执行get 方法会抛异常
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
*判断异步任务是否被取消
*/
boolean isCancelled();
/**
* 判断异步任务的执行是否结束
*/
boolean isDone();
/**
* 获取异步任务的执行结果,如果任务未运行结束,则该方法会使当前线程阻塞
* 异步任务运行错误,调用get方法会抛出ExecutionException异常
*/
V get() throws InterruptedException, ExecutionException;
// 同get方法,但是允许设置最大超时时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
接下来看看Callback接口,该接口与Runnable接口非常相似,但是Runnable作为任务接口最大的问题就是无法返回最终的计算结果,因此在JDK1.5版本中引入了Callable泛型接口,它允许任务执行结束后返回结果。
package java.util.concurrent;
@FunctionalInterface
// 泛型接口
public interface Callable<V>
{
V call() throws Exception;
}
1.1 快速认识
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
});
// 此时会阻塞,大概等10s,上面的线程执行结束后,就会返回
future.get();
// 线程执行的返回值:hello
System.out.println("线程执行的返回值:" + future.get());
}
1.2 取消任务
- 取消异步正在执行的任务:如果一个异步任务的运行特别耗时,那么Future是允许对其进行取消操作的。
调用cancel方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
});
// 等一下,让任务执行起来
= TimeUnit.SECONDS.sleep(2);
// 取消正在执行的异步任务(参数为false,任务虽然已被取消但是不会将其中断)
future.cancel(false);
System.out.println(future.isCancelled()); // true
// 但是调用get方法会抛出异常
future.get();
}
如果在执行future的cancel方法时指定参数为true,那么在callable接口中正在运行的可中断方法会被立即中断,比如sleep方法:
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("中断任务");
}
return "hello";
});
// 等一下,让任务执行起来
TimeUnit.SECONDS.sleep(2);
// 取消正在执行的异步任务(true,任务会将其中断)
future.cancel(true);
System.out.println(future.isCancelled()); // true
}
true
中断任务
1.3 异常捕获
Runnable类型的任务中,run()方法抛出的异常(运行时异常)只能被运行它的线程捕获(有可能会导致运行线程死亡),但是启动运行线程的主线程却很难获得Runnable任务运行时出现的异常信息。,我们可以通过设置UncaughtExceptionHandler的方式来捕获异常,但是这种方式的确不够优雅,并且也无法精确地知道是执行哪个任务时出现的错误,Future则是通过捕获get方法异常的方式来获取异步任务执行的错误信息的
通过设置UncaughtExceptionHandler的方式来捕获异常,可参考Hook线程和捕获线程执行异常
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
throw new RuntimeException("运行异常。。。");
});
// 等一下,让任务执行起来
TimeUnit.SECONDS.sleep(2);
try {
future.get();
} catch (ExecutionException e) {
// 这里就可以捕获到线程运行出现的异常信息
System.out.println(e.getClass().getSimpleName() + " -> " + e.getMessage());
}
}
运行输出:
ExecutionException -> java.lang.RuntimeException: 运行异常。。。
2 ExecutorService与Future
线程池中通过submit方法提交一个callable异步执行任务并且返回future的操作。线程池中还提供了其他更多任务执行的方法
2.1 提交Runnable类型任务
Submit方法除了可以提交执行Callable类型的任务之外,还可以提交Runnable类型的任务并且有两种重载形式
/**
提交Runnable类型的任务并且返回Future,待任务执行结束后,
通过该future的get方法返回的结果始终为null。
**/
public Future<?> submit(Runnable task);
/**
上面的提交Runnable类型的任务虽然会返回Future,
但是任务结束之后通过future却拿不到任务的执行结果,
而通过该submit方法则可以。
**/
public <T> Future<T> submit(Runnable task, T result)
2.2 invokeAny
ExecutorService允许一次性提交一批任务,但是其只关心第一个完成的任务和结果,比如,我们要获取某城市当天天气情况的服务信息,在该服务中,我们需要调用不同的服务提供商接口,最快返回的那条数据将会是显示在APP或者Web前端的天气情况信息,这样做的好处是可以提高系统响应速度,提升用户体验
invokeAny是一个阻塞方法,它会一直等待直到有一个任务完成
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Callable<Integer>> callables = new ArrayList<>();
// 定义10 Callable
for (int i = 0; i < 10; i++) {
callables.add(() -> {
int random = ThreadLocalRandom.current().nextInt(30);
// 随机休眠,模拟不同接口访问的不同时间开销
TimeUnit.SECONDS.sleep(random);
System.out.println("Task: " + random + " completed in Thread " + currentThread().getName());
return random;
});
}
// 执行
try {
Integer res = executorService.invokeAny(callables);
System.out.println("result: " + res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
输出
Task: 3 completed in Thread pool-1-thread-8
result: 3
在ExecutorService中还提供了invokeAny的重载方法,该方法允许执行任务的超时设置。
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException,
ExecutionException, TimeoutException;
2.3 invokeAll
invokeAll方法同样可用于异步处理批量的任务,但是该方法关心所有异步任务的运行,invokeAll方法同样也是阻塞方法,一直等待所有的异步任务执行结束并返回结果
3 Future的不足之处
- 无法被动接收异步任务的计算结果:虽然我们可以主动将异步任务提交给线程池中的线程来执行,但是待异步任务结束后,主(当前)线程无法得到任务完成与否的通知,它需要通过get方法主动获取计算结果。Google Guava的Future提供了解决方法,可参考:Google Guava的Future
- Future间彼此孤立:有时某一个耗时很长的异步任务执行结束以后,你还想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future彼此之间都是孤立的
- Future没有很好的错误处理机制:截至目前,如果某个异步任务在执行的过程中发生了异常错误,调用者无法被动获知,必须通过捕获get方法的异常才能知道异步任务是否出现了错误,从而再做进一步的处理。