1、基本介绍
Runnable 是一个接口,在它里面只声明了一个 run()方法,由于 run()方法返回值为 void 类型,所以在执行完任务之后无法返回任何结果。
Callable 位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明 了一个方法,只不过这个方法叫做 call(),这是一个泛型接口,call()函数返回的类型就是传递进来的 V 类型。
Future 就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。在Future类中一共有5个方法,如下图所示。
cancel(boolean mayInterruptIfRunning) 方法:取消任务,如果任务已经完成、已经被取消或由于某些其他原因而无法取消,则此尝试将失败。如果成功,并且在调用{@code cancel}时此任务尚未开始,则该任务永远不会运行。如果任务已经开始,则{@code mayInterruptIfRunning}参数确定是否应中断执行该任务的线程以尝试停止该任务。
get() 方法:获取返回结果
get(long timeout, TimeUnit unit) 方法:获取结果时设置等待时长
isCancelled() 方法:如果此任务在正常完成之前被取消,则返回{@code true}。
isDone() 方法:如果此任务完成,则返回{@code true}。
由于Future 只是一个接口,无法直接用来创建对象使用,因此就有了下面的FutureTask。FutureTask 类实现了RunnableFuture 接口,RunnableFuture 继承了Runnable接口和Future 接口,而FutureTask 实现了RunnableFuture 接口。所以它既可以作为Runnable被线程执行,又可以作为Future 得到Callable 的返回值。FutureTask,可取消的异步计算。此类提供{@link Future}的基本实现,其中包含启动和取消计算,查询以查看计算是否完成以及检索计算结果的方法。只有在计算完成后才能检索结果;如果计算尚未完成,则{@code get}方法将阻塞。一旦计算完成,就不能重新开始或取消计算(除非使用{@link #runAndReset}调用计算)。UML类图如下所示,可清晰看到几个类的关系。
因此我们通过一个线程运行Callable,但是Thread 不支持构造方法中传递Callable的实例,所以我们需要通过FutureTask 把一个Callable 包装成Runnable,然后再通过这个FutureTask 拿到Callable 运行后的返回值。要new 一个FutureTask的实例,有两种方法,如下图所示。
2、代码示例
类说明:Future的基本使用
package cn.lspj.ch2.future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 类说明:Future的使用
*/
public class UseFuture {
/**
* 实现Callable接口,允许有返回值
*/
private static class UseCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable 子线程开始计算。。。。。。");
int num = 0;
for(int i=0;i<5000;i++){
num += i;
}
System.out.println("Callable 线程计算结果为 num =" + num);
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseCallable useCallable = new UseCallable();
// 包装
FutureTask futureTask = new FutureTask(useCallable);
new Thread(futureTask).start();
System.out.println("获取子线程Callable计算返回结果:" + futureTask.get());
}
}
执行结果如下:
类说明:Future的使用,演示在计算过程中中断任务
package cn.lspj.ch2.future;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 类说明:Future的使用,演示在计算过程中中断任务
*/
public class UseFuture {
/**
* 实现Callable接口,允许有返回值
*/
private static class UseCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable 子线程开始计算。。。。。。");
int num = 0;
for(int i=0;i<5000;i++){
if(Thread.currentThread().isInterrupted()){
System.out.println("Callable 子线程计算任务被中断了。。。。。。。。。。");
return null;
}
Thread.sleep(1);
num += i;
System.out.println("num=" + num);
}
System.out.println("Callable 线程计算结果为 num =" + num);
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseCallable useCallable = new UseCallable();
// 包装
FutureTask futureTask = new FutureTask(useCallable);
new Thread(futureTask).start();
Thread.sleep(35);
Random r = new Random(10);
if(r.nextInt() > 5){
System.out.println("获取子线程Callable计算返回结果:" + futureTask.get());
} else {
System.out.println("cancle..............");
futureTask.cancel(true);
}
}
}
执行结果如下:
从执行结果来看,在任务执行计算的过程中调用task的cancel(boolean mayInterruptIfRunning)方法中断任务是可以达到中断任务的目的,但是需要在执行计算的业务逻辑中使用Thread.currentThread().isInterrupted()判断任务是否中断,否则计算任务是不会主动中断的。
更多精彩内容请关注“菜鸟技术栈”微信公众号
CSDN:
https://blog.csdn.net/lspj201007186/article/details/106247283
往期内容:
JAVA并发编程:线程并发工具类CountDownLatch与CyclicBarrier的作用、应用场景和实战
JAVA并发编程:线程并发工具类Fork-Join原理分析及实战
算法:JAVA实现归并排序
算法:插入排序
并发编程:线程中Join方法的使用示例分析
并发编程:线程基础、JAVA新启线程的方式
详解Java 8 中使用Stream将List转为Map
MYSQL之limit基本用法