Java中Runnable和Callable接口的区别

566

在Java中设计并发线程时,使用RunnableCallable接口有什么区别?你为什么会选择其中之一?


2
阅读本页后,如需进一步讨论,请参见Callable是否应优先于Runnable? - barfuin
14个回答

496

请参考这里的解释。

Callable接口类似于Runnable,两者都设计用于可能被另一个线程执行的类实例。然而,Runnable不会返回结果,也不能抛出已检查异常。


6
没问题。Runnable任务可以使用Thread类或ExecutorService运行,而Callables只能使用后者运行。 - cem çetin

303

RunnableCallable在应用程序中的区别是什么?区别只在于Callable中有返回参数吗?

基本上是这样。请参见此问题的答案以及Callablejavadoc

如果Callable可以完成所有Runnable的工作,那么两者的存在有何必要?

因为Runnable接口不能完成Callable的全部工作!

Runnable自Java 1.0就开始存在,但是Callable是在Java 1.5中引入的...用于处理Runnable不支持的使用情况。理论上,Java团队可以改变Runnable.run()方法的签名,但是这将破坏与1.5之前的代码的二进制兼容性,迁移旧Java代码到更新的JVM时需要重新编码,这是绝对不可行的。Java力求向后兼容...这一点一直是Java在商业计算领域最大的卖点之一。

显然,有些用例不需要返回结果或抛出已检查异常。对于这些用例,使用Runnable比使用Callable<Void>并从call()方法返回一个虚拟(null)值更加简洁。


16
我想知道你从哪里得到这段历史记录的。这非常有用。 - spiderman
8
@prash - 基本事实可以在旧教科书中找到,比如Java in a Nutshell的第一版。请让我知道是否还需要翻译其他内容。 - Stephen C
7
(@prash-同时...开始使用Java 1.1时代的Java。) 同时,通过开始在Java 1.1时代使用Java,他成功地跟上了技术进步的步伐。 - Stephen C
1
@StephenC 如果我正确理解了你的回答,你是在暗示 Runnable 存在(在很大程度上)是为了向后兼容。但是,在某些情况下,实现(或要求)Callable 接口可能是不必要或过于昂贵的(例如,在 ScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit) 中)。因此,即使历史并没有强制出当前的结果,保持两个接口在语言中仍然有好处吗? - max
1
@max - 嗯,我说过那句话,现在我仍然同意。然而,那只是一个次要的原因。但即便如此,我怀疑如果没有维护兼容性的必要,Runnable本来就会被修改。"boilerplate"中的return null;是一个薄弱的论点。(至少,在你可以忽略向后兼容性的假设情况下,这将是我的决定...) - Stephen C
显示剩余2条评论

101
  • Callable需要实现call()方法,而Runnable需要实现run()方法。
  • Callable可以返回值,但Runnable不能。
  • Callable可以抛出已检查异常,但Runnable不能。
  • Callable可与ExecutorService#invokeXXX(Collection<? extends Callable<T>> tasks)方法一起使用,但Runnable不能。
  • public interface Runnable {
        void run();
    }
    
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    21
    ExecutorService.submit(Runnable task) 也存在并且非常有用。 - Yair Kukielka
    Runnable也可以通过以下方式与ExecutorService一起使用: 1)ExecutorService.execute(Runnable) 2)ExecutorService.submit(Runnable) - Azam Khan
    2
    还有Executor.submit(Callable<T> task),但你不能使用一组Runnable任务的集合来调用invokeAll或invokeAny Collection<? extends Callable<T>> tasks。 - nikli

    40
    我在另一个博客中找到这个,可以更详细地解释一下这些差异:
    尽管两个接口都由希望在不同执行线程中执行的类实现,但两个接口之间存在一些差异,包括: - Callable<V> 实例返回类型为 V 的结果,而 Runnable 实例则不返回结果 - Callable<V> 实例可能抛出已检查异常,而 Runnable 实例不能
    Java 的设计师们感觉有必要扩展 Runnable 接口的功能,但又不想影响 Runnable 接口的使用。这可能是他们决定在 Java 1.5 中新增了名为 Callable 的单独接口,而不是修改已经存在于 Java 1.0 中的 Runnable 接口的原因。

    34

    让我们看看何时会使用Runnable和Callable。

    Runnable和Callable都在不同的线程上运行,而非调用线程。但是Callable可以返回值,而Runnable不能。那么这到底应用在哪里呢。

    Runnable:如果您有一个“点火并忘记”任务,则使用Runnable。将代码放入Runnable中,当调用run()方法时,您可以执行任务。调用线程实际上不关心您何时执行任务。

    Callable:如果您试图从任务中检索值,则使用Callable。现在仅使用Callable将无法完成工作。您需要将Future包装在Callable周围,并在future.get()上获取值。在此处,调用线程将被阻塞,直到Future返回结果,而后者正在等待Callable的call()方法执行。

    因此,请考虑针对目标类的接口,其中定义了两种包装方法:Runnable和Callable。调用类将随机调用您的接口方法,不知道哪个是Runnable,哪个是Callable。Runnable方法将异步执行,直到调用Callable方法为止。在这里,调用类的线程将被阻塞,因为您正在从目标类中检索值。

    注意:在目标类内部,您可以在单个线程执行器上调用Callable和Runnable,使该机制类似于串行调度队列。只要调用者调用您的Runnable包装方法,调用线程将非常快速地执行而不会阻塞。一旦它调用一个Future方法中包装的Callable,则必须阻塞,直到所有其他排队项目都被执行。然后该方法将返回值。这是一种同步机制。


    18

    Callable接口声明了call()方法,你需要提供泛型作为call()返回的对象类型 -

    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    

    Runnable是一个接口,声明了一个run()方法。当你使用runnable创建一个线程并在其上调用start()时,该方法会被调用。你也可以直接调用run(),但这只会在同一线程中执行run()方法。

    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used 
         * to create a thread, starting the thread causes the object's 
         * <code>run</code> method to be called in that separately executing 
         * thread. 
         * <p>
         * The general contract of the method <code>run</code> is that it may 
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    

    总结几个显著的区别:

    1. Runnable对象不返回结果,而Callable对象返回结果。
    2. Runnable对象不能抛出已检查异常,而Callable对象可以抛出异常。
    3. Runnable接口自Java 1.0以来就存在,而Callable只是在Java 1.5中引入的。

    一些相似之处包括:

    1. 实现RunnableCallable接口的类的实例可能由另一个线程执行。
    2. 通过submit()方法,CallableRunnable接口的实例都可以被ExecutorService执行。
    3. 两者都是函数接口,可用于Lambda表达式,自Java8开始。

    ExecutorService接口中的方法有:

    <T> Future<T> submit(Callable<T> task);
    Future<?> submit(Runnable task);
    <T> Future<T> submit(Runnable task, T result);
    

    14

    Oracle文档中这些接口的目的:

    Runnable接口应该被任何一个类实现,其实例旨在由Thread执行。该类必须定义一个名为run的无参方法。

    Callable:返回结果并可能抛出异常的任务。实现者定义一个名为call的无参方法。

    Callable接口类似于Runnable。然而,Runnable不返回结果,也不能抛出已检查的异常。

    其他区别:

    1. 您可以传递Runnable以创建Thread。但是您无法通过传递Callable作为参数来创建新线程。只能将Callable传递给ExecutorService实例。

    2. 使用Runnable进行fire and forget调用。使用Callable验证结果。

    3. Callable可传递至invokeAll方法,而Runnable则不行。方法invokeAnyinvokeAll执行最大的批量执行=>执行任务集合,然后等待至少一个或全部完成。

    4. 微小的差异:要实现的方法名称=> run()用于Runnablecall()用于Callable


    14

    如前所述,Callable是一个相对较新的接口,作为并发包的一部分被引入。Callable和Runnable都可以与执行器一起使用。Thread类(它本身实现了Runnable)仅支持Runnable。

    您仍然可以将Runnable与执行器一起使用。Callable的优点在于,您可以将其发送到执行器,并立即获得Future结果,在执行完成后将更新该结果。同样的功能也可以使用Runnable实现,但在这种情况下,您必须自己管理结果。例如,您可以创建保存所有结果的结果队列。其他线程可以等待此队列并处理到达的结果。


    我想知道Java中线程抛出异常的示例是什么?主线程能否捕获该异常?如果不能,我就不会使用Callable。Alex,你对此有什么见解吗?谢谢! - trillions
    1
    代码在自定义线程中运行,与其他代码一样可能会引发异常。要在其他线程中捕获它,您需要进行一些努力,或者使用自定义通知机制(例如基于监听器),或者使用Future,或者添加一个钩子来捕获所有未捕获的异常:http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler%28java.lang.Thread.UncaughtExceptionHandler%29 - AlexR
    太棒了!谢谢,Alex! :) - trillions
    2
    我点赞这个答案是因为它正确地表明了必须使用可调用对象的线程池模型(如果直接理解的话)。看起来不太幸运的是,无法扩展“Thread”以有效利用“Callable”接口,以便单个线程可以定制为执行可调用任务和其他开发人员可能需要的任务。如果有任何人认为我说错了,请指出我的错误... - user1941660
    1
    如果您的意思是字面上的线程池,那就不正确。您可以向由单个线程支持的执行器服务传递 RunnableCallable 而不是线程池。请参见 Executors.newSingleThreadExecutor()Executors.newSingleThreadScheduledExecutor()。如果您所说的“线程池”是指 Executors 框架,则应理解 Java 5 中添加 Executors 框架的目的是为了使开发人员无需直接处理 Thread 类。通常情况下,您不再需要扩展 Thread 来实现并发工作。 - Basil Bourque

    14

    Callable 和 Runnable 的区别如下:

    1. Callable 是在 JDK 5.0 中引入的,而 Runnable 是在 JDK 1.0 中引入的。
    2. Callable 有 call() 方法,而 Runnable 有 run() 方法。
    3. Callable 的 call 方法返回值,而 Runnable 的 run 方法不返回任何值。
    4. call 方法可以抛出已检查异常,但 run 方法不能抛出已检查异常。
    5. Callable 使用 submit() 方法将其放入任务队列中,而 Runnable 使用 execute() 方法将其放入任务队列中。

    强调一下,重要的是 受检异常(checked Exception),而非 RuntimeException。 - BertKing

    10
    +----------------------------------------+--------------------------------------------------------------------------------------------------+
    |              Runnable                  |                                           Callable<T>                                            |
    +----------------------------------------+--------------------------------------------------------------------------------------------------+
    | Introduced in Java 1.0 of java.lang    | Introduced in Java 1.5 of java.util.concurrent library                                           |
    | Runnable cannot be parametrized        | Callable is a parametrized type whose type parameter indicates the return type of its run method |
    | Runnable has run() method              | Callable has call() method                                                                       |
    | Runnable.run() returns void            | Callable.call() returns a generic value V                                                        |
    | No way to propagate checked exceptions | Callable's call()“throws Exception” clause so we can easily propagate checked exceptions further |                                                                     |
    +----------------------------------------+--------------------------------------------------------------------------------------------------+
    

    Java的设计者们感觉需要扩展Runnable接口的功能,但他们不想影响Runnable接口的使用,这可能是为什么他们选择了在Java 1.5中使用名为Callable的单独接口,而不是更改已经成为Java自Java 1.0以来一部分的现有Runnable接口。 来源

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接