在哪里使用可调用以及在哪里使用可调用接口



我对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替换为Callables(这很简单,两者都是单方法接口!),停止操作Threads 以支持Futures,并将管道留给ExecutorService

我的疑问是,如果 Callable 能够完成 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 Callable?

也许他们确实有旧代码(Java 1.4?)。也许他们不知道高级抽象的好处,而更喜欢低级Thread的东西?

一般来说,自从并发 API 出现以来,绝对没有理由更喜欢使用Threads。

与可调用接口相比,实现可调用接口是否有任何额外的开销?

在"实施Callable"与Runnable中,没有。但是新Callable的东西是有代价的,因为在内部,这一切都归结为Threads和Runnables(至少,当前的实现是这样做的)。但您实际上可能会获得性能,因为新功能包括更容易的线程重用!

所以,是的,并发 API 有其成本,但它在任何标准用例中都是绝对、完全可以忽略不计的,但它也有新的优势,使其在许多方面都变得更好,包括性能。

此外,如前所述,您可以从API中获得大量新的可能性(参见Fork/Join框架,参见Java 8中的并行流,...),并减少了对上述功能的任何"自定义","自制"并发代码的需求,这是众所周知的难以正确处理。

收益/成本比完全有利于"新"并发 API。

我的怀疑是,如果 Callable 能够做 Runnable 的所有事情,为什么这么多人使用 Runnable 而不是 callable?与 Runnable Inteface 相比,实现 Callable 接口是否有任何额外的开销?

  1. 使用Runnable界面进行即发即弃调用,尤其是当您对任务执行的结果不感兴趣时。
  2. 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程序员谈论"鸭子打字",这是谷歌的另一个好词。

相关内容

  • 没有找到相关文章

最新更新