我对Java很陌生,我正在经历多线程的概念,在经历使用多线程的各种实现时,我经历了这两个概念。 Java 中 Runnable 和 Callable 接口之间的区别问题指定了两者之间的区别以及在哪里使用。
我的疑问是,如果 Callable 能够完成 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 Callable? 与 Runnable Inteface 相比,实现 Callable 接口是否有任何额外的开销?
在 Java 5 版本中出现java.util.concurrent
包之前,实际上没有其他选择来进行并发计算,只能直接操作Thread
。
您可以直接操作线程,例如通过子类化:
public class MyThread extends Thread {
public void run() {
myComputation();
}
}
或者你可以通过创建一个Runnable
来做同样的事情,这是首选的方式(主要是因为你不需要子类化任何东西,这提供了明确的关注点分离,更好的代码重用或通常,更好的组合):
public class MyRunnable implements Runnable {
public void run() {
mycomputation();
}
}
new Thread(new MyRunnable()).start();
但无论你使用哪种方式,你都必须创建、启动、操作、加入线程。这有缺点:您可以创建多少个线程?这贵吗?(在某些时候,它是,尽管随着每个JVM实现它变得越来越便宜。
另外,这个 API 本身也有开发人员必须克服的限制:如果我想从Runnable
返回一个值,无论如何看到签名都不允许它怎么办?我如何知道我的Runnable
是否因Exception
而失败?诸如异常处理程序之类的东西允许线程这样做,但是这是一个重复的,容易出错的任务。
进入java.util.concurrent
包!它为所有这些(以及更多)提供了答案!
你想为多个"工作单元"重用线程(无论是Runnable
还是不?):你可以。想知道"工作单元"是完成还是失败:可以。想要返回一个值:可以。想要动态地将某些任务优先于其他任务?确定。需要安排任务的能力?可以做。等等。
将Runnable
替换为Callable
s(这很简单,两者都是单方法接口!),停止操作Thread
s 以支持Future
s,并将管道留给ExecutorService
。
我的疑问是,如果 Callable 能够完成 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 Callable?
也许他们确实有旧代码(Java 1.4?)。也许他们不知道高级抽象的好处,而更喜欢低级Thread
的东西?
一般来说,自从并发 API 出现以来,绝对没有理由更喜欢使用Thread
s。
与可调用接口相比,实现可调用接口是否有任何额外的开销?
在"实施Callable
"与Runnable
中,没有。但是新Callable
的东西是有代价的,因为在内部,这一切都归结为Thread
s和Runnable
s(至少,当前的实现是这样做的)。但您实际上可能会获得性能,因为新功能包括更容易的线程重用!
所以,是的,并发 API 有其成本,但它在任何标准用例中都是绝对、完全可以忽略不计的,但它也有新的优势,使其在许多方面都变得更好,包括性能。
此外,如前所述,您可以从API中获得大量新的可能性(参见Fork/Join框架,参见Java 8中的并行流,...),并减少了对上述功能的任何"自定义","自制"并发代码的需求,这是众所周知的难以正确处理。
收益/成本比完全有利于"新"并发 API。
我的怀疑是,如果 Callable 能够做 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 callable?与 Runnable Inteface 相比,实现 Callable 接口是否有任何额外的开销?
- 使用
Runnable
界面进行即发即弃调用,尤其是当您对任务执行的结果不感兴趣时。 Callable
接口的实现没有额外的开销。
与文档页面的主要区别
Callable
接口与Runnable
类似,因为两者都是为实例可能由另一个线程执行的类而设计的。但是,Runnable
不返回结果,并且不能引发已检查的异常。
让我们检查一下 AbstractExecutorService 的源代码
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Object> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
我没有看到执行Callable
任务的任何开销,因为Callable
内部使用RunnableFuture<T>
一个重要的区别:Runnable
接口中的run()
方法返回 void;Callable
接口中的call()
方法返回T
类型的对象。 这使您可以轻松访问响应对象。
您可以通过创建私有数据成员并提供 getter 来对Runnable
的具体实现执行相同的操作,但语法糖是甜蜜的。
为什么这么多人使用Runnable而不是callable?
Callable 是 jdk 中极具竞争力的新功能,它附带了 Executor 框架,为线程执行提供了更大的灵活性。
在 中实现可调用接口是否有任何额外的开销 与 Runnable Inteface 相比?
我不这么认为,如果您遵循任何标准的执行程序文档,这是非常简单的。
我的怀疑是,如果 Callable 能够做 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 callable?
这个问题无异于问:"为什么Java有静态类型? 接口是编译时类型签名的声明,仅此而已;任何两个接口之间的唯一区别是参数和返回值的编译时类型。
那么为什么Java有静态类型呢? 不幸的是,这个问题超出了StackOverflow的范围。 您可以使用 Google 查找有关静态类型语言与动态类型语言的所有信息。
另外,如果你想一睹不同的世界,你可以尝试编写一些Ruby代码。 Ruby 中没有接口,因为没有静态类型。 Ruby程序员谈论"鸭子打字",这是谷歌的另一个好词。