并发 - Java并发工具 Future模式与Callable类

并发 - Java并发工具 Future模式与Callable类

Future模式就是纯粹的异步, 先提交一个任务, 然后过一会再去检查任务是否完成. 被调用的Future会立刻返回. 下个月慢慢的要准备搬家了, 要换地方住了, 还有点舍不得呢, 毕竟现在的房子是从结婚之后就一直住的. Future模式的整体使用 例子 API Future模式的整体使用 还记得之

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()来使用.
LICENSED UNDER CC BY-NC-SA 4.0
Comment