# 十、Future

# 1. Runnable 的缺点

  1. 没有返回值。
  2. 不能抛出 CheckedException。

而 Callable 接口解决了上面这两个问题:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

# 2. Future

# 2.1 作用

  • 让子线程去做一件事,然后自己做自己的事情,需要的时候再来拿子线程的执行结果,然后再做自己的事情。

# 2.2 与 Callable 的关系

我们可以有那个 future.get() 方法来获得 Callable 接口返回的执行结果,还可以通过 future.isDone() 来判断任务是否已经执行完了,以及取消这个任务,限时获取任务的结果等。

call() 未执行完毕之前,调用 get() 的线程(假定此时是主线程)会被阻塞,直到 call() 方法返回了结果后,此时 future.get() 才会得到该结果,然后主线程才会切换到 RUNNABLE 状态。

# 2.3 get()

get() 方法的行为取决于 Callable 任务的状态,只有以下 5 种情况:

  1. 任务正常完成,get() 方法回立刻返回结果。
  2. 任务尚未开始(任务还没开始或进行中),get() 将阻塞直到任务完成。
  3. 任务执行过程中抛出 Exception,get() 方法回抛出 ExecutionException。这里抛出的异常,是 call() 执行时产生的那个异常,看到的异常类型是 java.util.concurrent.ExecutionException。不论 call() 执行时抛出的异常类型是什么,最后 get() 方法抛出的异常类型都是 ExecutionException
  4. 任务被取消,get() 方法回抛出 CancellationException
  5. 任务超时,get(long timeout, TimeUnit unit) 会抛出 TimeoutException

# 2.4 cancel()

取消任务的执行。

  1. 如果这个任务还没有开始执行,那么任务会被正常取消,未来也不会执行,方法返回 true。

  2. 如果任务已经完成,或者已取消,那么 cancel() 方法会执行失败,返回 false。

  3. 如果任务执行一半了,如果传入 true,那么会中断 task,如果传入 false,那么 task 还会继续进行,返回 true。

    cancel(true) 适用于:

    • 任务能够处理 interrupt。

    cancel(false) 适用于:

    • 未能处理 interrupt 的任务。
    • 不清楚任务是否支持取消。
    • 需要等待已经开始的任务执行完成。

# 2.5 isDone()

判断线程是否执行完毕(完毕不意味着成功)。

# 2.6 isCancelled()

任务是否被取消。

# 3. 示例

# 3.1 用法一

首先,我们需要给线程池提交我们的任务,提交时线程池会立刻返回给我们一个空的 Future 对象。当线程的任务一旦执行完毕,线程池便会把该结果填入之前的 Future 对象中,此时我们便可以通过该对象来获取任务执行的结果。

image-20210325111433371
/**
 * 用法1:首先,我们需要给线程池提交我们的任务,提交时线程池会立刻返回给我们一个空的 Future 对象。
 *       当线程的任务一旦执行完毕,线程池便会把该结果填入之前的 Future 对象中,
 *       此时我们便可以通过该对象来获取任务执行的结果。
 */
public class FutureCase1 {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        Future<Integer> future = service.submit(new CallableTask());
        try {
          	//获取任务执行结果
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    static class CallableTask implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return new Random().nextInt(100);
        }
    }
}

# 3.2 用法二

多个任务的情况下,我们可以用 Future 数组来获取结果。

/**
 * 用法2:多个任务,用 Future 数组来获取结果
 */
public class FutureCase2 {


    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        ArrayList<Future<Integer>> futures = new ArrayList<>();

        //批量接收结果
        for (int i = 0; i < 20; i++) {
            Future<Integer> future = service.submit(new CallableTask());
            futures.add(future);
        }

        for (int i = 0; i < 20; i++) {
            Future<Integer> integerFuture = futures.get(i);
            try {
                System.out.println(integerFuture.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

    }
    
    static class CallableTask implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return new Random().nextInt();
        }
    }
}

# 3.3 用法三

任务执行过程中抛出异常。异常不是直接抛出,而是 get() 执行的时候才抛出异常。

/**
 * 用法3:任务执行过程中抛出异常。异常不是直接抛出,而是 **get() 执行的时候才抛出异常。**
 */
public class FutureCase3 {

    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(20);

        Future<Integer> future = service.submit(new CallableTask());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("抛出InterruptedException");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.out.println("抛出ExecutionException");
        }

        System.out.println("任务是否执行结束: " + future.isDone());
    }

    static class CallableTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            for (int i = 1; i <= 5; i++) {
                System.out.println("第" + i + "次休息");
                Thread.sleep(100);
            }
            throw new IllegalArgumentException("callable 抛出异常...");
        }
    }
}

结果:

  • 只有 get() 执行了才会抛出异常。
  • 不管 Task 抛出什么异常,最后捕获的都是 ExecutionException。
  • 异常结束 isDone 也是返回 true。
第1次休息
第2次休息
第3次休息
第4次休息
第5次休息
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: callable 抛出异常...
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at ch10_future.FutureCase3.main(FutureCase3.java:18)
Caused by: java.lang.IllegalArgumentException: callable 抛出异常...
	at ch10_future.FutureCase3$CallableTask.call(FutureCase3.java:37)
	at ch10_future.FutureCase3$CallableTask.call(FutureCase3.java:30)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
抛出ExecutionException
任务是否执行结束: true

# 3.4 用法四

演示 get(long time, TimeUnit unit) 的超时情况,需要注意超时后的处理。

  • cancel(false):不中断 Task

    public class FutureCase4 {
    
        private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告");
        private static final ExecutorService service = Executors.newFixedThreadPool(5);
    
        /**
         * 广告
         */
        static class Ad{
            String name;
            public Ad(String name){
                this.name = name;
            }
            @Override
            public String toString() {
                return "Ad{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
    
        static class FetchAdTask implements Callable<Ad>{
            @Override
            public Ad call() throws Exception {
                try{
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    System.out.println("sleep期间被中断了");
                    return new Ad("被中断时的默认广告");
                }
                return new Ad("网络顺畅时的真广告" + new Random().nextInt() + "");
            }
        }
    
        public void printAd(){
            Future<Ad> future = service.submit(new FetchAdTask());
            Ad ad;
            try {
                ad = future.get(2000,TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                //中断异常
                ad = new Ad("被中断时候的默认广告");
            } catch (ExecutionException e) {
                //执行异常
                ad = new Ad("执行时异常的默认广告");
            } catch (TimeoutException e) {
                //超时异常
                //1.赋值一个默认的
                ad = new Ad("超时时的默认广告");
                //2. 提醒
                System.out.println("超时了,未获取到广告");
                //3. 结束任务,传入 false,不中断 task
                boolean cancel = future.cancel(false);
                System.out.println("取消成功与否:" + cancel);
            }
            service.shutdown();
            System.out.println(ad);
        }
    
    
        public static void main(String[] args) {
            new FutureCase4().printAd();
        }
    
    }
    

    结果:

    超时了,未获取到广告
    取消成功与否:true
    Ad{name='超时时的默认广告'}
    
  • cancel(true):超时后中断 task

    public class FutureCase4 {
        private static final Ad DEFAULT_AD = new Ad("无网络时候的默认广告");
        private static final ExecutorService service = Executors.newFixedThreadPool(5);
        /**
         * 广告
         */
        static class Ad{
            String name;
            public Ad(String name){
                this.name = name;
            }
            @Override
            public String toString() {
                return "Ad{" +
                        "name='" + name + '\'' +
                        '}';
            }
        }
    
        static class FetchAdTask implements Callable<Ad>{
            @Override
            public Ad call() throws Exception {
                try{
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    System.out.println("sleep期间被中断了");
                    return new Ad("被中断时的默认广告");
                }
                return new Ad("网络顺畅时的真广告" + new Random().nextInt() + "");
            }
        }
    
        public void printAd(){
            Future<Ad> future = service.submit(new FetchAdTask());
            Ad ad;
            try {
                ad = future.get(4000,TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                //中断异常
                ad = new Ad("被中断时候的默认广告");
            } catch (ExecutionException e) {
                //执行异常
                ad = new Ad("执行时异常的默认广告");
            } catch (TimeoutException e) {
                //超时异常
                //1.赋值一个默认的
                ad = new Ad("超时时的默认广告");
                //2. 提醒
                System.out.println("超时了,未获取到广告");
                //3. 结束任务,传入 true,中断 task
                boolean cancel = future.cancel(true);
                System.out.println("取消成功与否:" + cancel);
            }
            service.shutdown();
            System.out.println(ad);
        }
    
        public static void main(String[] args) {
            new FutureCase4().printAd();
        }
    
    }
    
    • 结果:
    超时了,未获取到广告
    sleep期间被中断了 #多了这一句
    取消成功与否:true
    Ad{name='超时时的默认广告'}
    

# 4. FutureTask

# 4.1 作用

image-20210325115945230
  1. 用 FutureTask 来获取 Future 和任务的结果。
  2. FutureTask 是一种包装器,可以把 Callable 转化成 Future 和 Runnable,它同时实现二者的接口。
  3. FutureTask 既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值。

# 4.2 使用方式

  1. 创建一个实现 Callable 的实现类
  2. 重写 call() 方法,将此线程需要做的事情放在 call() 里面
  3. 创建 Callable 实现类的对象
  4. 将上述对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 对象
  5. 将 FutureTask 对象作为 Runnabke 对象参数传递到 Thread 构造器中,创建 Thread 对象
  6. 调用 Thread 对象的 start() 启动线程
  7. 可以通过 FutureTask 对象的 get() 获取 call() 方法的返回值等信息

# 4.3 示例

package ch10_future;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author Hedon Wang
 * @create 2021-03-25 12:00 PM
 */
public class FutureTaskDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3. 创建 Callable 实现类的对象
        Task task = new Task();
      	//4. 将上述对象作为参数传递到 FutureTask 构造器中,创建 FutureTask 对象
        FutureTask<Integer> futureTask = new FutureTask<>(task);
      	//5. 将 FutureTask 对象作为 Runnabke 对象参数传递到 Thread 构造器中,创建 Thread 对象
      	//6. 调用 Thread 对象的 start() 启动线程
        new Thread(futureTask).start();
      	//7. 可以通过 FutureTask 对象的 get() 获取 call() 方法的返回值等信息
        Integer res = futureTask.get();
        System.out.println("子线程执行结果:" + res);
    }
		
  	//1. 创建一个实现 Callable 的实现类
    static class Task implements Callable<Integer>{
      	//2. 重写 call() 方法,将此线程需要做的事情放在 call() 里面
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "子线程正在计算");
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

# 5. 注意点

  1. 用 for 循环来批量接收任务执行结果的时候,会出现有的快有的慢的情况,后面的得等前面的 get() 执行完毕才能执行。
  2. Future 的生命周期不能后退(Runnable 可以重复传入 new Thread() 中)。
上次更新: 9/17/2021, 12:28:06 PM