Future模式就是纯粹的异步, 先提交一个任务, 然后过一会再去检查任务是否完成. 被调用的Future会立刻返回.

下个月慢慢的要准备搬家了, 要换地方住了, 还有点舍不得呢, 毕竟现在的房子是从结婚之后就一直住的.

  1. Future模式的整体使用
  2. 例子
  3. API

Future模式的整体使用

还记得之前线程池中有两个方法, submit和execute, 都接受Runnable和Callable的对象, 但是这两个方法有区别, 区别就是submit会返回一个Future对象, 而execute只是执行而已.

Java对更高效的使用锁, 做出了一些努于给原来计数器的counter域加上了一个线程安全的包装, 这个适用于侵入性不是很强的情况下修改原来的类.

使用Future模式的主要顺序是:

  1. 创建一个Callable对象, Callable带有泛型, 就是要返回的结果.
  2. 使用Callable对象创建异步任务, 这个异步任务不是简单的Thread对象, 而是一个FutureTask<V>对象, 其中的泛型也就是返回的结果类型. FutureTask对象的构造器接受Callable类型.
  3. 将异步任务进行提交, 比如向一个线程池中提交.
  4. 等待需要FutureTask的结果的时候, 调用FutureTask的方法检查是否完成, 直到完成后获取数据.

例子

按照上边的步骤来试验一下, 先创建一个Callable对象, 模拟需要花费很多时间的工作:

import java.util.concurrent.Callable;

public class MyTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            stringBuilder.append(i);
            stringBuilder.append(" ");
        }

        return stringBuilder.toString();
    }
}

这个任务模拟了一个10秒钟生成一个字符串的任务. 然后使用这个Callable对象来创建一个FutureTask对象:

public static void main(String[] args) {
    FutureTask<String> futureTask = new FutureTask<>(new MyTask());
}

这里有一个继承关系, FutureTask同时继承Runnable和Future接口, 而Future接口就是要包装的异步结果的对象, 使用.get()方法就可以获取结果.

之后创建线程池, 然后提交这个任务, 之后只要找这个FutureTask对象要结果即可:

public static void main(String[] args) throws InterruptedException, ExecutionException {

    //上一步的创建FutureTask的内容
    FutureTask<String> futureTask = new FutureTask<>(new MyTask());

    //创建线程池
    ExecutorService pool = Executors.newCachedThreadPool();

    //把futureTask提交给线程池, 与提交不带返回值的Runnable类似
    pool.submit(futureTask);

    pool.shutdown();

    //主线程不会阻塞在提交任务上, 而且用来判断是否完成也不会阻塞
    while (!futureTask.isDone()) {

        System.out.println(System.currentTimeMillis() + " 还没有完成任务.");

        Thread.sleep(1000);
    }

    //直到完成了任务, 才会显示出结果
    System.out.println("完成任务了, 结果 = " + futureTask.get());

}

这段程序运行的时候, 显示如下:

1594461850067 还没有完成任务.
1594461851087 还没有完成任务.
1594461852087 还没有完成任务.
1594461853088 还没有完成任务.
1594461854088 还没有完成任务.
1594461855088 还没有完成任务.
1594461856089 还没有完成任务.
1594461857089 还没有完成任务.
1594461858089 还没有完成任务.
1594461859089 还没有完成任务.
完成任务了, 结果 = 0 1 2 3 4 5 6 7 8 9

可以看到, 每次去询问是否完成任务, 都不会阻塞, 主线程可以自己做自己的事情. 需要注意的就是把任务提交后, 依然直接使用任务对象去拿结果就可以了.

这就是Callable加上Future模式的威力, Callable其实本来就是做这个用途的.

FutureTask的API

FutureTask的常用API有:

  1. get(), 获取结果
  2. get(long time, TimeUnit timeUnit), 带时间的获取
  3. isDone(), 是否任务完成或者被取消, 这里特别注意, 如果取消的话, isDone()也返回true ,所以这个的意思应该是Future任务得到了一个确定的结果, 即要么计算成功要么取消.
  4. cancel(boolean isI), 撤销任务, 里边的参数是一个布尔值
  5. isCancelled(), 是否已经被取消

这里要注意的是, isDone()在成功和取消的时候都会返回true, 所以一般需要搭配isCancelled()来使用.