详解Java中Callable和Future的区别

目录
  • Java中为什么需要Callable
  • Callable和Runnable的区别
  • Future和RunnableFuture
  • 不使用Callable和Future,仅使用Runnable实现相同功能

Java中为什么需要Callable

在java中有两种创建线程的方法:

一种是继承Thread类,重写run方法:

public class TestMain {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}
class MyThread extends Thread {
    public void run() {
        System.out.println("MyThread running...");
    }
}

第二种是使用Runnable创建一个线程:

public class TestMain {
    public static void main(String[] args) {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread created with runnable running...");
            }
        };
        Thread t1 = new Thread(r1);
        t1.start();
    }
}

其实这两种方式,底层都是执行Thread类的run方法:

无论使用这里的哪种方式创建线程,都无法在线程结束时return一个返回值。但是在非常多的场景下,我们都需要在线程执行结束时,将执行的结果封装为一个返回值返回给主线程(或者调用者线程)。因此java在1.5版本时,在java.util.concurrent包引入了Callable接口,用于线程执行完时return一个返回值。

Callable和Runnable的区别

Runnable和Callable都是接口,分别定义如下:

package java.lang;
​
/**
 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.
 * <p>
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
package java.util.concurrent;
​
/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

可以看出,CallableRunnable主要有两点区别:

  • 有返回值;
  • 可以抛出异常(这里抛出的异常,会在future.get()时可以通过ExectionException捕获);

因此可以看出,Callable更加实用。这里举个Callable使用的例子:

Callable callable = new Callable<Integer>() {
  @Override
  public Integer call() throws Exception {
    int i = new Random().nextInt(5);
    try {
      Thread.sleep(i * 1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return i;
  }
};

虽然Callable接口的call方法可以返回执行结果,但是有两个问题需要解决:

  • 线程的创建只能通过Runnable,通过Callable又如何创建线程?
  • 如何获取执行结果?

答案是FutureRunnableFuture

Future和RunnableFuture

Future是一个接口,看下定义:

package java.util.concurrent;
​
/**
 * A {@code Future} represents the result of an asynchronous
 * computation.  Methods are provided to check if the computation is
 * complete, to wait for its completion, and to retrieve the result of
 * the computation.  The result can only be retrieved using method
 * {@code get} when the computation has completed, blocking if
 * necessary until it is ready.  Cancellation is performed by the
 * {@code cancel} method.  Additional methods are provided to
 * determine if the task completed normally or was cancelled. Once a
 * computation has completed, the computation cannot be cancelled.
 * If you would like to use a {@code Future} for the sake
 * of cancellability but not provide a usable result, you can
 * declare types of the form {@code Future<?>} and
 * return {@code null} as a result of the underlying task.
 *
 * @see FutureTask
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface Future<V> {
​
    /**
     * Attempts to cancel execution of this task.  This attempt will
     * fail if the task has already completed, has already been cancelled,
     * or could not be cancelled for some other reason. If successful,
     * and this task has not started when {@code cancel} is called,
     * this task should never run.  If the task has already started,
     * then the {@code mayInterruptIfRunning} parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.
     *
     * <p>After this method returns, subsequent calls to {@link #isDone} will
     * always return {@code true}.  Subsequent calls to {@link #isCancelled}
     * will always return {@code true} if this method returned {@code true}.
     *
     * @param mayInterruptIfRunning {@code true} if the thread executing this
     * task should be interrupted; otherwise, in-progress tasks are allowed
     * to complete
     * @return {@code false} if the task could not be cancelled,
     * typically because it has already completed normally;
     * {@code true} otherwise
     */
    boolean cancel(boolean mayInterruptIfRunning);
​
    /**
     * Returns {@code true} if this task was cancelled before it completed
     * normally.
     *
     * @return {@code true} if this task was cancelled before it completed
     */
    boolean isCancelled();
​
    /**
     * Returns {@code true} if this task completed.
     *
     * Completion may be due to normal termination, an exception, or
     * cancellation -- in all of these cases, this method will return
     * {@code true}.
     *
     * @return {@code true} if this task completed
     */
    boolean isDone();
​
    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     */
    V get() throws InterruptedException, ExecutionException;
​
    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     * @throws TimeoutException if the wait timed out
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

可以看出,Future可以用来表示线程的未来执行结果:一个容器,这个容器内将来存放的是线程的执行结果,线程执行完之前该容器内没有值,但是线程一旦执行成功(Callablecall方法返回之后),就会将结果存入该容器。从Future的接口定义可看出,Future不仅支持阻塞获取执行结果,还支持取消任务的执行,判断任务是否执行完成等。因此通过Future,主线程(或者调用者线程)可以跟进子现场的执行情况。

Callable其实和Runnable很像,都会执行一个任务,只不过Callable可以返回执行的结果。一般将执行结果封装到Future,调用者线程即可以通过Future获取Callable的执行结果了。因此,一般Callable会和Future搭配使用。

但是问题来了:java创建线程,需要Runnable,获取执行结果又需要Future。因此RunnableFuture来了:

可以看出,通过RunnableFuture,既可以创建线程,又可以获取线程的执行结果,当然RunnableFuture也是一个接口,我们一般情况下会使用它的具体实现类FutureTask

那可能又有人要问了,Callable又是如何建立联系的呢?看下FutureTask的使用方式就明白了:

public class TestMain {
    public static void main(String[] args) {
        Callable callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i = new Random().nextInt(5);
                try {
                    Thread.sleep(i * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return i;
            }
        };
​
        /**
         * callable创建futureTask
         * FutureTask实现了RunnableFuture接口,因此即是Runnable又是Future
         * 作为Runnable可以传入Thread创建线程并执行
         * 作为Future,可以用来获取执行的结果。
         * 这里创建出来的futureTask对象有人称为"具柄"或者"存根",大家可以理解为用来获取线程执行结果的一个"引用"即可。
         */
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
​
        // 作为Runnable使用
        Thread thread = new Thread(futureTask);
        thread.start();
​
        try {
            // 作为Future使用
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

因此FutureTaskCallableRunnable的桥梁。

不使用Callable和Future,仅使用Runnable实现相同功能

下面我们看下,如果不使用CallableFuture,仅使用Runnable如何实现返回值。

public class TestMain {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.start();
        Object o = myRunnable.get();
        System.out.println(o);
    }
}
​
class MyRunnable implements Runnable {
    // 存储执行结果
    private Object outCome = null;
​
    @Override
    public void run() {
        int i = new Random().nextInt(5);
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 存储执行结果
        outCome = i;
        // 产出结果后唤醒等待的get方法
        synchronized (this) {
            notifyAll();
        }
    }
​
    public synchronized Object get() {
        while(outCome == null) {
            try {
                // 等待产出结果
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return outCome;
    }
}

可以看出,通过Runnable实现更加麻烦,因此这也体现出了Callable+Future的优势。

以上就是详解Java中Callable和Future的区别的详细内容,更多关于Java Callable Future区别的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java CompletableFuture 异步超时实现深入研究

    目录 前言 常见使用方式 存在的问题 分析 现有做法 解决方式 JDK 9 JDK 8 前言 作者:京东科技 张天赐 JDK 8 是一次重大的版本升级,新增了非常多的特性,其中之一便是 CompletableFuture.自此从 JDK 层面真正意义上的支持了基于事件的异步编程范式,弥补了 Future 的缺陷. 在我们的日常优化中,最常用手段便是多线程并行执行.这时候就会涉及到 CompletableFuture 的使用. 常见使用方式 下面举例一个常见场景. 假如我们有两个 RPC 远程调用

  • java CompletableFuture异步任务编排示例详解

    目录 前言 同步串行 异步串行 并行任务 多任务结果合并计算 任一任务完成 快速失败 注意 前言 在之前的项目开发中,都没怎么使用过CompletableFuture的功能,只听说过和异步编程有关.为了能够在将来有需要的时候用得上,这两天花了点时间学习了一下,并简单地总结一下如何使用CompletableFuture完成异步任务编排. 先创建一个自定义的线程池,后续所有代码都会使用到: private static final ThreadPoolExecutor THREAD_POOL_EXE

  • java异步编程CompletableFuture使用示例详解

    目录 一.简单介绍 二.常见操作 1.使用默认线程池 2.使用自定义线程池 3.获取线程的执行结果 三.处理异步结算的结果 四.异常处理 五.组合 CompletableFuture 六.并行运行多个 CompletableFuture 七.案例 1.从多个平台获取书价格 2.从任意一个平台获取结果就返回 一.简单介绍 CompletableFuture 同时实现了 Future 和 CompletionStage 接口. public class CompletableFuture<T> i

  • 详解Java中Callable和Future的区别

    目录 Java中为什么需要Callable Callable和Runnable的区别 Future和RunnableFuture 不使用Callable和Future,仅使用Runnable实现相同功能 Java中为什么需要Callable 在java中有两种创建线程的方法: 一种是继承Thread类,重写run方法: public class TestMain { public static void main(String[] args) { MyThread t1 = new MyThre

  • 详解Java中HashSet和TreeSet的区别

    详解Java中HashSet和TreeSet的区别 1. HashSet HashSet有以下特点: 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置. 简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个

  • 详解JAVA中implement和extends的区别

    详解JAVA中implement和extends的区别 extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承,Java中不支持多重继承,但是可以用接口来实现,这样就要用到implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了比如class A extends B implements C,D,E implements是一个类实现一个接口用的关键字,他是用来实现接口中定义的抽象方法. 还有几点需要注意: (1

  • 详解java中接口与抽象类的区别

    详解java中接口与抽象类的区别 1.abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系.但是,一个类却可以实现多个interface. 2.在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的. 3.abstract c

  • 详解Java中wait和sleep的区别

    1.概述 在这篇简短的文章中,我们将看一下核心Java 中的标准sleep()和wait()方法,并了解它们之间的差异和相似之处. 2. wait和sleep之间的一般差异 简单地说,wait()是一个用于线程同步的实例方法. 它可以在任何对象上调用,因为它在java.lang.Object上定义,但它只能从synchronized块中调用.它释放对象的锁定,以便另一个线程可以跳入并获取锁. 另一方面,Thread.sleep()是一个可以从任何上下文调用的静态方法.Thread.sleep()

  • 详解Java中int和Integer的区别

    基本数据类型和引用类型 Java是面向对象的编程语言,一切都是对象,但是为了编程的方便还是引入了基本数据类型,为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换,对应如下: 原始类型:boolean,char,byte,short,int,long,float,double 包装类型:Boolean,Character,Byte

  • 详解java中float与double的区别

    float是单精度类型,精度是8位有效数字,取值范围是10的-38次方到10的38次方,float占用4个字节的存储空间 double是双精度类型,精度是17位有效数字,取值范围是10的-308次方到10的308次方,double占用8个字节的存储空间 当你不声明的时候,默认小数都用double来表示,所以如果要用float的话,则应该在其后加上f 例如:float a=1.3; 则会提示不能将double转化成float  这成为窄型转化 如果要用float来修饰的话,则应该使用float a

  • 详解Java中Vector和ArrayList的区别

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复. 3个具体实现类的相关区别如下: 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对

  • 详解Java中AbstractMap抽象类

    jdk1.8.0_144 下载地址:http://www.jb51.net/softs/551512.html AbstractMap抽象类实现了一些简单且通用的方法,本身并不难.但在这个抽象类中有两个方法非常值得关注,keySet和values方法源码的实现可以说是教科书式的典范. 抽象类通常作为一种骨架实现,为各自子类实现公共的方法.上一篇我们讲解了Map接口,此篇对AbstractMap抽象类进行剖析研究. Java中Map类型的数据结构有相当多,AbstractMap作为它们的骨架实现实

  • 详解Java中异步转同步的六种方法

    目录 一.问题 应用场景 二.分析 三.实现方法 1.轮询与休眠重试机制 2.wait/notify 3.Lock Condition 4.CountDownLatch 5.CyclicBarrier 6.LockSupport 一.问题 应用场景 应用中通过框架发送异步命令时,不能立刻返回命令的执行结果,而是异步返回命令的执行结果. 那么,问题来了,针对应用中这种异步调用,能不能像同步调用一样立刻获取到命令的执行结果,如何实现异步转同步? 二.分析 首先,解释下同步和异步 同步,就是发出一个调

随机推荐