【发布时间】:2019-11-04 06:41:48
【问题描述】:
在 Java 中设计并发线程时使用Runnable 和Callable 接口有什么区别,为什么要选择其中一个?
【问题讨论】:
-
更多讨论,阅读本页后,见Shall Callable be preferred over Runnable?
标签: java multithreading interface runnable callable
在 Java 中设计并发线程时使用Runnable 和Callable 接口有什么区别,为什么要选择其中一个?
【问题讨论】:
标签: java multithreading interface runnable callable
见解释here。
Callable 接口类似于 可运行,因为两者都是经过设计的 对于实例为的类 可能被另一个人执行 线。 但是,Runnable 不会 返回结果并且不能抛出 检查异常。
【讨论】:
Runnable和Callable的应用有什么区别。区别仅在于Callable中存在的返回参数吗?
基本上,是的。请参阅this question 的答案。还有javadoc for Callable。
如果
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) 值更简洁。
【讨论】:
Runnable 存在(很大程度上)是出于向后兼容性的原因。但是是否存在实现(或要求)Callable 接口(例如,在ScheduledFuture<?> ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit) 中)不必要或过于昂贵的情况?那么即使历史并没有强制当前结果,维护语言中的两个接口是否也有好处?
Runnable 会被修改 如果没有保持兼容性的必要性。 return null; 的“样板”是一个弱论点。 (至少,这将是我的决定……在您可以忽略向后兼容性的假设环境中。)
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;
}
【讨论】:
我在另一个博客中发现了这个,可以解释更多differences:
虽然这两个接口都是由希望在不同执行线程中执行的类实现的,但是这两个接口之间几乎没有区别:
Callable<V> 实例返回 V 类型的结果,而 Runnable 实例不返回。Callable<V> 实例可能会抛出已检查异常,而 Runnable 实例则不会Java 的设计者觉得需要扩展Runnable 接口的功能,但他们不想影响Runnable 接口的使用,这可能就是他们选择单独的原因在 Java 1.5 中名为 Callable 的接口比更改已经存在的 Runnable。
【讨论】:
让我们看看在哪里可以使用 Runnable 和 Callable。
Runnable 和 Callable 都在与调用线程不同的线程上运行。但是 Callable 可以返回一个值,而 Runnable 不能。那么这在哪里真正适用。
Runnable:如果你有一个火而忘记的任务,那么使用 Runnable。将您的代码放在 Runnable 中,当调用 run() 方法时,您可以执行您的任务。当你执行你的任务时,调用线程真的不在乎。
Callable :如果您尝试从任务中检索值,请使用 Callable。现在可单独调用将无法完成这项工作。您将需要一个 Future 来包装您的 Callable 并在 future.get() 上获取您的值。这里调用线程将被阻塞,直到 Future 返回结果,然后等待 Callable 的 call() 方法执行。
所以考虑一个目标类的接口,你定义了 Runnable 和 Callable 包装的方法。调用类会随机调用你的接口方法,不知道哪个是可运行的,哪个是可调用的。 Runnable 方法将异步执行,直到调用 Callable 方法。这里调用类的线程将阻塞,因为您正在从目标类中检索值。
注意:在您的目标类中,您可以在单个线程执行器上调用 Callable 和 Runnable,使这种机制类似于串行调度队列。因此,只要调用者调用您的 Runnable 包装方法,调用线程就会非常快速地执行而不会阻塞。一旦它调用包装在 Future 方法中的 Callable,它将不得不阻塞,直到所有其他排队的项目都被执行。只有这样,该方法才会返回值。这是一种同步机制。
【讨论】:
Callable 接口声明了call() 方法,您需要提供泛型,因为 Object 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() 方法的接口,当您使用可运行对象创建线程并在其上调用 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();
}
总结几个显着的区别是
Runnable 对象不返回结果,而 Callable 对象返回结果。Runnable 对象不能抛出检查异常,而 Callable 对象可以抛出
例外。Runnable 接口自 Java 1.0 以来就已存在,而 Callable 仅被引入
在 Java 1.5 中。一些相似之处包括
ExecutorService接口中的方法是
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
【讨论】:
oracle 文档中这些接口的用途:
Runnable 接口应该由其实例打算由Thread 执行的任何类实现。该类必须定义一个没有参数的方法,称为run。
Callable:返回结果并可能抛出异常的任务。实现者定义了一个没有参数的方法,称为 call。
Callable 接口类似于Runnable,因为两者都是为实例可能由另一个线程执行的类设计的。但是,Runnable 不会返回结果,也不会抛出已检查的异常。
其他区别:
您可以通过Runnable 来创建Thread。但是您不能通过将Callable 作为参数传递来创建新线程。您只能将 Callable 传递给 ExecutorService 实例。
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
使用Runnable 进行火灾并忘记呼叫。使用Callable 验证结果。
Callable 可以传递给invokeAll 方法,这与Runnable 不同。方法 invokeAny 和 invokeAll 执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部完成
细微差别:要实现的方法名称 => run() 对应于Runnable 和call() 对应于Callable。
【讨论】:
正如这里已经提到的,Callable 是一个相对较新的接口,它是作为并发包的一部分引入的。 Callable 和 Runnable 都可以与执行程序一起使用。类 Thread(实现 Runnable 本身)仅支持 Runnable。
您仍然可以将 Runnable 与执行程序一起使用。 Callable 的优点是您可以将其发送给 executor 并立即返回 Future 结果,该结果将在执行完成时更新。 Runnable 也可以实现相同的功能,但在这种情况下,您必须自己管理结果。例如,您可以创建将保存所有结果的结果队列。其他线程可以在这个队列上等待并处理到达的结果。
【讨论】:
Future 或添加捕获所有未处理异常的钩子来执行一些工作:docs.oracle.com/javase/6/docs/api/java/lang/…
Thread 以有意义地使用Callable 接口,以便可以定制单个线程来执行可调用的事情和开发人员可能想要的其他事情。如果任何阅读此评论的人认为我错了,我想知道得更好......
Runnable 或Callable 传递给由单个线程而不是线程池支持的执行程序服务。请参阅 Executors.newSingleThreadExecutor() 和 Executors.newSingleThreadScheduledExecutor()。如果“线程池”是指 Executors 框架,请理解在 Java 5 中添加 Executors 框架的目的是让开发人员不必直接处理 Thread 类。通常,您不应再为您的并发工作扩展 Thread。
Callable 和 Runnable 的区别如下:
【讨论】:
+----------------------------------------+--------------------------------------------------------------------------------------------------+
| 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 的接口,而不是更改已经存在的 Runnable 接口,该接口自 Java 1.0 以来一直是 Java 的一部分。 source
【讨论】:
Callable 和 Runnable 两者相似,都可以用于实现线程。在实现 Runnable 的情况下,您必须实现 run() 方法,但在可调用的情况下,您必须实现 call() 方法,这两种方法以类似的方式工作,但可调用的 call() 方法具有更大的灵活性。它们之间存在一些差异。
Runnable 和 callable 的区别如下——
1) runnable 的 run() 方法返回 void,这意味着如果你希望你的线程返回一些你可以进一步使用的东西您对 Runnable run() 方法别无选择。有一个解决方案'Callable',如果你想以object的形式返回任何东西,那么你应该使用Callable而不是Runnable。可调用接口具有返回 Object 的方法 'call()'。
方法签名 - 可运行->
public void run(){}
可调用->
public Object call(){}
2) 在 Runnable run() 方法的情况下,如果出现任何已检查的异常,那么您必须需要使用 try catch 块处理,但在 的情况下Callable call() 方法你可以抛出检查异常如下
public Object call() throws Exception {}
3) Runnable 来自旧版 java 1.0 版本,但 callable 来自 Java 1.5 版本,带有 Executer 框架。
如果您熟悉 Executers,那么您应该使用 Callable 而不是 Runnable。
希望你能理解。
【讨论】:
Runnable (vs) Callable 在我们使用 Executer 框架时很重要。
ExecutorService 是Executor 的子接口,它同时接受Runnable 和Callable 任务。
早期的多线程可以使用接口Runnable从1.0开始来实现,但是这里的问题是在我们完成线程任务之后无法收集线程信息。为了收集数据,我们可能会使用静态字段。
示例分离线程来收集每个学生的数据。
static HashMap<String, List> multiTasksData = new HashMap();
public static void main(String[] args) {
Thread t1 = new Thread( new RunnableImpl(1), "T1" );
Thread t2 = new Thread( new RunnableImpl(2), "T2" );
Thread t3 = new Thread( new RunnableImpl(3), "T3" );
multiTasksData.put("T1", new ArrayList() ); // later get the value and update it.
multiTasksData.put("T2", new ArrayList() );
multiTasksData.put("T3", new ArrayList() );
}
为了解决这个问题,他们引入了 Callable<V>从 1.5 开始,它会返回结果并可能引发异常。
单一抽象方法:Callable 和 Runnable 接口都有一个抽象方法,这意味着它们可以在 java 8 的 lambda 表达式中使用。
public interface Runnable {
public void run();
}
public interface Callable<Object> {
public Object call() throws Exception;
}
有几种不同的方法可以将要执行的任务委托给ExecutorService。
execute(Runnable task):void 创建新线程但不会阻塞主线程或调用者线程,因为此方法返回 void。submit(Callable<?>):Future<?>、submit(Runnable):Future<?> 在您使用 future.get() 时创建新线程并阻塞主线程。使用接口 Runnable, Callable 与 Executor 框架的示例。
class CallableTask implements Callable<Integer> {
private int num = 0;
public CallableTask(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " : Started Task...");
for (int i = 0; i < 5; i++) {
System.out.println(i + " : " + threadName + " : " + num);
num = num + i;
MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
}
System.out.println(threadName + " : Completed Task. Final Value : "+ num);
return num;
}
}
class RunnableTask implements Runnable {
private int num = 0;
public RunnableTask(int num) {
this.num = num;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " : Started Task...");
for (int i = 0; i < 5; i++) {
System.out.println(i + " : " + threadName + " : " + num);
num = num + i;
MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
}
System.out.println(threadName + " : Completed Task. Final Value : "+ num);
}
}
public class MainThread_Wait_TillWorkerThreadsComplete {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("Main Thread start...");
Instant start = java.time.Instant.now();
runnableThreads();
callableThreads();
Instant end = java.time.Instant.now();
Duration between = java.time.Duration.between(start, end);
System.out.format("Time taken : %02d:%02d.%04d \n", between.toMinutes(), between.getSeconds(), between.toMillis());
System.out.println("Main Thread completed...");
}
public static void runnableThreads() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<?> f1 = executor.submit( new RunnableTask(5) );
Future<?> f2 = executor.submit( new RunnableTask(2) );
Future<?> f3 = executor.submit( new RunnableTask(1) );
// Waits until pool-thread complete, return null upon successful completion.
System.out.println("F1 : "+ f1.get());
System.out.println("F2 : "+ f2.get());
System.out.println("F3 : "+ f3.get());
executor.shutdown();
}
public static void callableThreads() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> f1 = executor.submit( new CallableTask(5) );
Future<Integer> f2 = executor.submit( new CallableTask(2) );
Future<Integer> f3 = executor.submit( new CallableTask(1) );
// Waits until pool-thread complete, returns the result.
System.out.println("F1 : "+ f1.get());
System.out.println("F2 : "+ f2.get());
System.out.println("F3 : "+ f3.get());
executor.shutdown();
}
}
【讨论】:
是一种与函数式编程相匹配的接口命名约定
//Runnable
interface Runnable {
void run();
}
//Action - throws exception
interface Action {
void run() throws Exception;
}
//Consumer - consumes a value/values, throws exception
interface Consumer1<T> {
void accept(T t) throws Exception;
}
//Callable - return result, throws exception
interface Callable<R> {
R call() throws Exception;
}
//Supplier - returns result, throws exception
interface Supplier<R> {
R get() throws Exception;
}
//Predicate - consumes a value/values, returns true or false, throws exception
interface Predicate1<T> {
boolean test(T t) throws Exception;
}
//Function - consumes a value/values, returns result, throws exception
public interface Function1<T, R> {
R apply(T t) throws Exception;
}
...
//Executor
public interface Executor {
void execute(Runnable command);
}
【讨论】:
除了所有其他答案:
我们不能将 Callable 传递/使用给单个线程来执行,即 Callable 只能在 Executor Framework 中使用。 但是,Runnable 可以传递给单个线程执行(new Thread(new CustomRunnable())),也可以在 Executor Framework 中使用。
【讨论】: