JUC高并发编程
七、Callable&Future接口
7.1)创建线程的方式
创建线程的方式有以下4种:
-
继承Thread类;
-
实现Runnable接口;
-
实现Callable接口;
-
调用线程池
Runnable接口缺少的一项功能是,当线程终止时(即run() 完成时),无法使线程返回结果,为了支持此功能, Java中提供了Callable接口。
7.2)Callable接口
Callable接口和Runnable接口相比较有以下特点:
-
为了实现Runnable需要实现不返回任何内容的run()方法,而对于 Callable,需要实现在完成时返回结果的call() 方法;
-
call() 方法可以抛出异常,而run() 则不能抛出异常;
-
为实现Callable而必须重写call方法,不能直接替换Runnable,因为Thread类的构造方法根本没有Callable
【解决该问题需要引入FutureTask,它和Runnable、Callable都有关联,Runnable接口有实现类FutureTask,FutureTask构造函数可以传递Callable】
Callable接口和Runnable接口代码如下:
//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
@Override
public void run() {
// ...
}
}
//实现Callable接口
class MyThread2 implements Callable {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" come in callable");
return 200;
}
}
7.3)Future 接口
当 call() 方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此可以使用Future对象。
将Future视为保存结果的对象,它可能暂时不保存结果,但将来会保存(一旦 Callable返回)。
Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式;要实现此接口必须重写相关方法,这里列出了比较重要的方法,如下:
-
public boolean cancel(boolean mayInterrupt):用于停止任务,如果尚未启动它将停止任务;如果已启动,则仅在mayInterrupt为true 时才会中断任务;
-
public Object get() :抛出InterruptedException,ExecutionException, 用于获取任务的结果; 如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果;
-
public boolean isDone():如果任务完成,则返回true,否则返回false ;
可以看到Callable和Future做两件事:Callable与Runnable类似,因为它封装了要在另一个线程上运行的任务;而Future用于存储从另一个线程获得的结果【future也可以与Runnable一起使用】。
总结:要创建线程,需要Runnable/Callable;为了获得结果,需要future。
7.4)FutureTask
Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。
可以通过为其构造函数提供Callable来创建 FutureTask;然后将FutureTask对象提供给Thread的构造函数以创建 Thread对象。从而间接地使用Callable创建线程。
7.4.1)FutureTask核心原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些任务交给Future对象在后台完成;
-
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态;
-
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
-
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法 一旦计算完成,就不能再重新开始或取消计算 ;
-
get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
-
get只计算一次,因此get方法放到最后执行。
7.4.2)FutureTask举例说明
例1:
老师上课口渴了,去买水不合适,讲课线程继续运行;
单开启线程让班长帮老师买水,把水买回来;
老师喝水时需要时候直接get水即可
例2:
3个同学计算, 1同学: 1+2...5 , 2同学:10+11+12....50, 3同学 100+200;
因为第2个同学计算量比较大,所以FutureTask单开启线程给2同学计算;
主线程先汇总1同学和3同学的计算结果 ,最后等2同学计算完成后,再统一汇总最终结果
核心思想:主线程中需要执行比较耗时的操作时,但又不想阻塞主线程,新增一个线程去处理耗时的操作,最终一次将所有操作汇总
7.5)Callable和Future代码示例
Callable 和 Future的代码示例如下:
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable接口创建线程
new Thread(new MyThread1(), "AA").start();
//Callable接口,报错
// new Thread(new MyThread2(),"BB").start();
//FutureTask实现Callable接口
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//lam表达式:FutureTask实现Callable接口
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 1024;
});
//创建一个线程
new Thread(futureTask2, "lucy").start();
new Thread(futureTask1, "mary").start();
while(!futureTask2.isDone()) {
System.out.println("futureTask2.isNotDone(),wait.....");
}
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName() + " come over");
}
}
输出:
futureTask2.isNotDone(),wait.....
futureTask2.isNotDone(),wait.....
...
futureTask2.isNotDone(),wait.....
mary come in callable
lucy come in callable
futureTask2.isNotDone(),wait.....
1024
200
main come over
7.6)小结
在主线程中需要执行比较耗时的操作,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成:
-
当主线程将来需要时就可以通过Future 对象获得后台作业的计算结果或者执行状态 ;
-
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
-
仅在计算完成时才能检索结果,如果计算尚未完成则阻塞 get 方法,一旦计算完成,就不能再重新开始或取消计算;
-
get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
-
只最终汇总计算一次。