【JUC】5.Callable和Future接口

1. Callable接口

常规的两种创建线程的方法:一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是 Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能, Java 中提供了 Callable 接口。

Callable接口的特点:

  • call() 方法有返回结果。
  • call() 方法可以抛出异常,但 run() 不能。
  • 实现Callable接口必须重写 call() 方法。
  • Thread类构造方法不能直接传入Callable对象。

2. Future接口

当 call() 方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。

将 Future 视为保存结果的对象,它可能暂时不保存结果,但将来会保存(一旦 Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:

  • 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。为了获得结果,需要 future。

3. FutureTask

Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 接口和 Future接口,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建 FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建 Thread 对象。因此间接地使用 Callable 创建线程。

核心原理(重点)

  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成。
  • 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态。
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。
  • 一旦计算完成,就不能再重新开始或取消计算。
  • get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
  • get 只计算一次,因此 get 方法放到最后。

4. 案例

/**
 * CallableDemo 案列
 */
public class CallableDemo {
    /**
     * 实现 runnable 接口
     */
    static class MyThread1 implements Runnable {
        /**
         * run 方法
         */
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "线程进入了 run 方法");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 实现 callable 接口
     */
    static class MyThread2 implements Callable {
        /**
         * call 方法
         *
         * @return
         * @throws Exception
         */
        @Override
        public Long call() throws Exception {
            try {
                System.out.println(Thread.currentThread().getName() + "线程进入了 call 方法,开始准备睡觉");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "睡醒了");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return System.currentTimeMillis();
        }
    }

    public static void main(String[] args) throws Exception {
        //声明 runable
        Runnable runable = new MyThread1();
        //声明 callable
        Callable callable = new MyThread2();
        //future-callable
        FutureTask<Long> futureTask = new FutureTask(callable);
        //线程二
        new Thread(futureTask, "线程二").start();
        for (int i = 0; i < 10; i++) {
            Long result = futureTask.get();
            System.out.println(result);
        }
        //线程一
        new Thread(runable, "线程一").start();
    }
}

5. 小结

  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态。
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
  • 只计算一次。
posted @ 2021-08-25 23:01  haojinglei  阅读(51)  评论(0)    收藏  举报