06-Callable & Future 接口

  1. 一、Callable接口
    1. 1. Callable接口创建线程
    2. 2. Future 接口
  2. 二、FutureTask

一、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 {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行了run方法");
    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    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> {

    @Override
    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