一、Callable接口
创建线程的多种方式:
- 继承Thread类
- 实现Runnable接口
- Callable接口
- 线程池
1. Callable接口创建线程
目前学习了有两种创建线程的方法,一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程,但是 Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口
比较Runnable接口和Callable接口
- Callable 中的 call() 计算结果,如果无法计算结果,会抛出异常
- Runnable 中的 run() 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用该对象的run方法总的来说:run() 没有返回值,不会抛出异常。而 call() 有返回值,会抛出异常
因为Thread的构造函数中没有Callable接口的参数设置,直接替换不可以,只能用下面这种线程创建方法(找一个类,即和 Runnable 接口有关系,又和 Callable 接口有关系), 发现 Runnable 接口有实现类 FutureTask(中间对象), FutureTask 的构造函数有Callable参数,通过 FutureTask 创建线程对象
Callable使用示例:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Runable 调用线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行了run方法");
}, "Runable").start();
// 使用Callable调用线程
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "执行了call方法");
return "Callable的call方法返回结果";
});
new Thread(futureTask, "Callable").start();
String result = futureTask.get();
System.out.println(result);
}
}
执行结果:
Runable执行了run方法
Callable执行了call方法
Callable的call方法返回结果
实现Callable接口的写法
//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了run方法");
}
}
//实现Callable接口
class MyThread2 implements Callable {
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" come in callable");
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable接口创建线程
new Thread(new MyThread1(),"MyThread1").start();
//Callable接口,报错,没有这个类型的构造方法
// new Thread(new MyThread2(),"BB").start();
//FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//lam表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
return 1024;
});
//创建一个线程
new Thread(futureTask2,"futureTask2").start();
new Thread(futureTask1,"futureTask1").start();
while(!futureTask2.isDone()) {
System.out.println("futureTask2 wait.....");
}
//调用FutureTask的get方法
System.out.println("futureTask2结果:" + futureTask2.get());
System.out.println("futureTask1结果:" + futureTask1.get());
System.out.println(Thread.currentThread().getName()+" come over");
}
}
执行结果:
futureTask2 wait.....
futureTask2 wait.....
MyThread1执行了run方法
futureTask2 wait.....
futureTask1 come in callable
futureTask2 come in callable
futureTask2 wait.....
futureTask2 wait.....
futureTask2结果:1024
futureTask1结果:200
main come over
2. Future 接口
当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。
将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:
public boolean cancel (boolean mayInterruptIfRunning) 用于停止任务。
如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterruptIfRunning 为 true时才会中断任务。
public boolean isCancelled()
观察任务是否取消成功
public V get() throws InterruptedException, ExecutionException 用于获取任务的结果。
如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
在指定时间内尝试获取执行结果,若超时则抛出超时异常
public boolean isDone() 如果任务完成,则返回 true,否则返回 false
使用示例:
public class FutureTest {
public static void main(String[] args) throws Exception {
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 初始化一个任务
Callable<String> task = new Task();
// 提交任务并获得Future的实例
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果(可能会阻塞等待结果)
String result =future.get();
System.out.println("任务执行结果:" + result);
// 任务执行完毕之后,关闭线程池(可选)
executor.shutdown();
}
}
class Task implements Callable<String> {
public String call() throws Exception {
// 执行下载某文件任务,并返回文件名称
System.out.println("thread name:" + Thread.currentThread().getName() + " 开始执行下载任务");
return "xxx.png";
}
}
执行结果:
thread name:pool-1-thread-1 开始执行下载任务
任务执行结果:xxx.png
多次调用 future.get()
方法,call()
方法中的打印语句不会重复执行,直接返回了结果,因为 Future 在第一次调用 get() 方法时缓存了结果。
二、FutureTask
Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程。
核心原理:(重点)
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成
- 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
- 一旦计算完成,就不能再重新开始或取消计算
- get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
- get 只计算一次,因此 get 方法放到最后
总结:
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
- 只计算一次
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com