您的位置:首页 > 编程语言 > Java开发

Java多线程学习笔记:Callable、Future、FutureTask

2018-11-08 10:14 274 查看
版权声明:原创内容,如需转载请联系作者。 https://blog.csdn.net/CrankZ/article/details/83856768

概述

创建线程的三种方式:

  1. 继承Thread,重写run方法
  2. 实现Runnable接口,重新run方法
  3. 实现Callable接口,重写call方法

前两种方式,一种是直接继承Thread,另外一种就是实现Runnable接口,这两种方式都是Java第一版就有的方法。 
这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。 
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。

  • Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能
  • Callable的call方法分可以抛出异常,而Runnable的run方法不能抛出异常。

什么是Executor

Executor仅仅是一个接口,只有一个方法execute(Runnable command),是在JDK1.5中引入的,主要是用来运行提交的可运行的任务。一般我们并不直接使用该接口,而是使用其不同的子接口,主要是ExecutorService,而通常情况,与ExecutorService一起使用的是Executors类,该类由著名的并发编程大师Doug Lea实现。Executor框架可以用来控制线程的启动、执行和关闭,可以简化并发编程的操作。

什么是ExecutorService

常用接口分析

在ExecutorService中提供了重载的submit()方法,该方法既可以接收Runnable实例又能接收Callable实例。对于实现Callable接口的类,需要覆写call()方法,并且只能通过ExecutorService的submit()方法来启动call()方法。那么既然存在Runnable接口,为什么还要添加Callable接口呢?这是因为Runnable不会返回结果,并且无法抛出经过检查的异常而Callable会返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样前者有返回值,而后者没有。

什么是Executors

Executors提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口:

  1. newCachedThreadPool()可缓存线程池,对于每个线程,如果有空闲线程可用,立即让它执行,如果没有,则创建一个新线程
  2. newFixedThreadPool()具有固定大小的线程池,如果任务数大于空闲的线程数,则把它们放进队列中等待
  3. newSingleThreadPool()大小为1的线程池,任务一个接着一个完成
  4. newScheduledThreadPool(int)调度型线程池,可控制线程最大并发数,支持定时及周期性任务执行,用来代替Timer

Callable

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。

继承关系

常用方法

[code]public interface Callable<V> {
    V call() throws Exception;
}

Callable的代码也非常简单,不同的是它是一个泛型接口,call()函数返回的类型就是创建Callable传进来的V类型。
学习Callable对比着Runnable。Callable与Runnable的功能大致相似,Callable功能强大一些,就是被线程执行后,可以返回值,并且能抛出异常

Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。可以通过get()方法获取执行结果,该方法会阻塞直到任务返回结果。

Future类位于java.util.concurrent包下,它是一个接口

常用方法

在Future接口中声明了5个方法,下面依次解释每个方法的作用:

  1. cancel(boolean mayInterruptIfRunning)方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  2. isCancelled()方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  3. isDone()方法表示任务是否已经完成,若任务完成,则返回true;
  4. get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  5. get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:

  1. 判断任务是否完成
  2. 能够中断任务
  3. 能够获取任务执行结果

Callable+Future代码

[code]public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<String> future = executorService.submit(new MyCallable());
    executorService.shutdown();
    System.out.println("do something in main");
    System.out.println("得到异步任务返回结果:" + future.get());
    System.out.println("主线程结束");
}

static class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Callable子线程开始");
        Thread.sleep(2000); // 模拟做点事情
        System.out.println("Callable子线程结束");
        return "MyCallable return";
    }
}

运行结果

上面是Future基本用法的代码以及并运行,我们可以知道:

  1. 线程是属于异步计算模型,所以你不可能直接从别的线程中得到方法返回值。 这时候,Future就出场了。
  2. Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直到call方法结束返回结果。
  3. Future引用对象指向的实际是FutureTask。

也就是说,总结一句话,Future可以得到别的线程任务方法的返回值。

FutureTask

Future是一个接口,而FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口(继承关系见下图)

FutureTask用于异步获取执行结果或取消执行任务的场景,它的主要功能有:

  • 可以判断任务是否完成
  • 可以获取任务执行结果
  • 可以中断任务

继承关系

常用接口分析

构造函数

[code]FutureTask(Callable<T> callable)
FutureTask(Runnable runnable, T result)

我们来分析一个这两个构造函数的源码

[code]public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;       // ensure visibility of callable
}
[code]public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;       // ensure visibility of callable
}

在这里我们可以了解到:

  1. FutureTask最终都是执行Callable类型的任务。
  2. 如果构造函数参数是Runnable,会被Executors.callable方法转换为Callable类型。

Callable+FutureTask代码

[code]public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();
    FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
    executorService.submit(futureTask);
    executorService.shutdown();
    System.out.println("do something in main");
    System.out.println("得到异步任务返回结果:" + futureTask.get());
    System.out.println("主线程结束");
}
static class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Callable子线程开始");
        Thread.sleep(2000); // 模拟做点事情
        System.out.println("Callable子线程结束");
        return "MyCallable return";
    }
}

运行结果

前面一直讲Callable区别于Runnable原因之一就是可以抛异常,下面代码演示下

还是用上面的例子

Callable+FutureTask抛异常代码

[code]public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
    executorService.submit(futureTask);
    executorService.shutdown();
    System.out.println("do something in main");
    try {
        System.out.println("得到异步任务返回结果:" + futureTask.get());
    } catch (Exception e) {
        System.out.println("捕获异常成功:" + e.getMessage());
    }
    System.out.println("主线程结束");

}
static class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("Callable子线程开始");
        Thread.sleep(2000); // 模拟做点事情
        System.out.println("Callable子线程结束");
        throw new NullPointerException("抛异常测试");
    }
}

运行结果

总结

  1. FutureCallable基本是成对出现的,Callable负责产生结果,Future负责获取结果。
  2. Callable接口类似于Runnable,只是Runnable没有返回值。
  3. Callable在被线程执行后,可以提供一个返回值,我们可以通过Futureget()方法拿到这个值
  4. get()方法会导致该线程会被阻塞,直到Future拿到Callable.call()方法的返回值
  5. Callablecall()方法可以抛出异常,我们可以在尝试执行get()方法时捕获这个异常,而Runnable并不能抛出异常
  6. FutureTask可以确保任务只执行一次

参考

https://www.jianshu.com/p/cf12d4244171

https://www.geek-share.com/detail/2669025745.html

https://segmentfault.com/a/1190000012291442

https://juejin.im/post/5a7b2c8b6fb9a0633a70eb54

http://codepub.cn/2016/02/01/Java-multi-thread-Callable-interface-and-thread-pool/

https://www.geek-share.com/detail/2620882102.html

 

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: