java多线程:Callable配合FutureTask实现创建异步任务并且获取其返回值
Future表示一个异步计算的结果。它提供了一种访问异步操作的结果的方式,以便在主线程中获取异步计算的结果。cancel(boolean): 取消任务。传入的参数为表示若任务开始执行了,是否要尝试中断该线程。参数true为进行尝试中断,false为不进行中断。isCanceled(): 获取该任务是否被中断了isDone(): 该任务是否已经完成get(): 获取该任务的返回值,若任务还未完成则会
今天在背面试题的时候,背到了一个关于多线程的题目: runnable 和 callable 有什么区别?
参考答案是:
- Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
Callable接口具体是如何和Future、FutureTask配合来获取异步执行的结果的呢?又发现了自己知识盲区。于是我看了多篇博客,自己手动敲了demo,写这篇博客总结了一下。
Future、FutureTask、Callable介绍
Future
Future表示一个异步计算的结果。它提供了一种访问异步操作的结果的方式,以便在主线程中获取异步计算的结果。
Future接口有5个方法:
- cancel(boolean): 取消任务。传入的参数为表示若任务开始执行了,是否要尝试中断该线程。参数true为进行尝试中断,false为不进行中断。
- isCanceled(): 获取该任务是否被中断了
- isDone(): 该任务是否已经完成
- get(): 获取该任务的返回值,若任务还未完成则会阻塞。
- get(Long, TimeUnit): 获取任务的返回值,若任务还未完成则会阻塞。参数为指定的阻塞的最长时间,超时会抛出TimeoutException。
FutureTask
FutureTask类实现了Future和Runnable接口,是我们实现线程异步执行,并能获取执行结果的封装的实现类。FutureTask有两个构造方法:
- FutureTask(Callable< V> callalbe): 通常的构造方法,Callable为异步任务,通过futureTask的get()方法可以阻塞地获取任务的返回值。
- FutureTask(Runnable runnable, V result): runnable为异步任务,result保存了执行结果,当futureTask的get()方法返回的就是result对象。当然,我们若是关心任务是否执行完成而不关心任务的返回值时,可以使用该构造方法。
Callable
Callable是java多线程中一个函数式接口,它是在java1.5的时候出现的,它能够返回线程执行结果并且能够抛出线程执行过程中的异常。Callable的出现主要是为了弥补继承Thread或实现Runnable接口的线程执行完成后,无法获得线程执行后的结果。
函数式接口:一个接口中只包含有一个方法。接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰。
看到Callable的源码我们会发现:Callable是个有泛型的接口,其中唯一的方法call有返回值,返回值的类型就是Callable传入的泛型。如果要实现一个异步任务,那么异步任务类实现Callable接口重写call方法,需要异步执行的代码就写在call方法中,同时返回执行结果。
实战演练
实现思路
当使用Callable和FutureTask来获取异步任务的返回值时,具体的步骤如下:
创建一个实现了Callable接口的任务。Callable接口包含一个call()方法,用于执行异步任务,并返回计算结果。
创建一个FutureTask对象,将Callable任务作为参数传入FutureTask的构造函数中。这样可以将Callable任务转换为一个FutureTask任务,使其具备异步计算的能力。
之后有两者创建线程的方式,一个是手动new Thread()创建线程执行方法,一个是配合线程池创建线程。
配合线程池使用: 创建一个ExecutorService对象,例如通过Executors工厂类创建一个线程池。
将FutureTask提交给ExecutorService执行,使用executor.submit(futureTask)方法。
在需要获取异步任务的返回值时,调用FutureTask的get()方法。如果异步任务尚未完成,get()方法会阻塞直到任务完成并返回结果。
最后记得关闭ExecutorService来释放资源,通过executor.shutdown()方法。
具体示例
使用Callable和FutureTask并行计算1+…+10和20+…+30的结果
手动new Thread()使用
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 这里用lambda匿名表达式新建了一个Callable的实现
FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
int res = 0;
for (int i = 0; i <= 10; i++) {
res += i;
}
return res;
});
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
int res = 0;
for (int i = 20; i <= 30; i++) {
res += i;
}
return res;
});
// FutureTask实现了Runnable接口,所以可以作为参数构造Thread()
Thread thread1 = new Thread(futureTask1);
Thread thread2 = new Thread(futureTask2);
// 实际上FutureTask的run方法就是在运行Callable的call方法
thread1.start();
thread2.start();
// 先获取任务1的值,未完成则会阻塞;获取任务2的值同理
System.out.println(futureTask1.get() + futureTask2.get());
}
配合线程池使用
线程池为我们封装好了线程,使用线程池实现异步计算也更加优雅:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 这里线程池submit执行的是call方法,返回的是FutureTask对象
// 这里用lambda匿名表达式新建了一个Callable的实现
Future<Integer> future1 = threadPoolExecutor.submit(()->{
int res = 0;
for (int i = 0; i <= 10; i++) {
res += i;
}
return res;
});
Future<Integer> future2 = threadPoolExecutor.submit(()->{
int res = 0;
for (int i = 20; i <= 30; i++) {
res += i;
}
return res;
});
System.out.println(future1.get() + future2.get());
}
更多推荐
所有评论(0)