1.用法
Callable是一个Interface。相当于把线程封装了一个“返回值”。方便程序员借助多线程的方式计算结果。
【实例】创建线程计算1+2+3+...+1000,不使用Callable版本
/**
* Describe:不使用Callable计算1+2+3+...+1000的值
* User:lenovo
* Date:2023-03-30
* Time:14:50
*/
public class TestDemo1 {
private static int sum = 0;
//private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
sum = 0;
Thread t = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
sum += i;
}
});
t.start();
t.join();
System.out.println(sum);
}
}
此处我们可以发现,如果计算这个式子的值,我们就需要一个静态成员变量的配合,但是我们无法保证这个静态成员变量只是被我们这个代码使用,这就很容易出错。
创建线程计算1+2+3+...+1000,使用Callable版本
- 创建一个匿名内部类,实现Callable接口(类似于Runnable,用于描述线程具体要做的任务),Callable带有泛型参数,泛型参数表示返回值的类型;
- 重写Callable的call方法,完成累加过程。直接通过返回值返回计算结果;
- 把callable实例使用FutureTask包装(因为我们并不知道这个线程什么时间完成,我们使用FutureTask来管理它,存放放回的结果)一下;
- 创建线程,线程的构造方法传入FutureTask.此时新线程就会执行FutureTask内部的Callable的call方法,完成计算。计算结果就放到FutureTask对象中。
- 在主线程中调用futureTask.get()能够阻塞等待新线程计算完毕。并获取到FutureTask中的结果
public class TestDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
可以看到,使用Callable和FutureTask之后,代码简化了好多。
理解Callable
Callable 和 Runnable相对,都是描述一个”任务“。Callable描述的是带有返回值的任务,
Runnable描述的是不带返回值的任务。
Callable通常需要搭配FutureTask来使用。FutureTask用来保存Callabel的返回结果。因为
Callable往往是在另一个线程中执行的,啥时候执行完并不确定。
FutureTask就可以负责这个等待结果出来的工作。
理解FutureTask
想象去学校食堂吃饭。点完餐后,后厨就开始做了,并给你一个号码。这个小票就是FutureTask。在饭做完的时候,我们就可以凭这个小票取餐了。
2.相关面试题
介绍一下Callable是什么?
Callable是一个interface.相当于把线程装了一个“返回值”。
Callable和Runnable是相对的,都是描述一个任务。Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务。
Callable需要搭配FutureTask来使用。FutureTask用来保存Callable的返回值。因为我们并不知道Callable往往是在另一个线程中执行的,啥时候完成时不确定的。
FutureTask就是为了负责这个等待结果出来的工作。