Java中Runnable和Callable接口之间的区别

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

357312 次浏览

见解释在这里

Callable接口类似于 可运行的,因为两者都是设计出来的 对于实例为的类 可能由另一个人执行 线程。然而,Runnable没有 返回结果,且不能抛出 检查例外。< /强> < / p >

我在另一篇博客中发现了这一点,可以更详细地解释差异:

虽然这两个接口都是由希望在不同的执行线程中执行的类实现的,但这两个接口之间有一些区别:

  • Callable<V>实例返回类型为V的结果,而Runnable实例则不返回。
  • Callable<V>实例可能会抛出受控异常,而Runnable实例则不会

Java的设计者觉得有必要扩展Runnable接口的功能,但他们不想影响Runnable接口的使用,这可能就是为什么他们在Java 1.5中使用一个名为Callable的单独接口,而不是改变已经存在的Runnable接口的原因。

RunnableCallable在应用上有什么不同?是否只有返回参数的差异在Callable?

基本上,是的。见这个问题的答案。还有Callable javadoc

如果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)更简洁。

正如这里已经提到的,Callable是一个相对较新的接口,它是作为并发包的一部分引入的。Callable和Runnable都可以与执行器一起使用。Thread类(实现Runnable本身)只支持Runnable。

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

  • 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;
    }
    

让我们看看在哪里可以使用Runnable和Callable。

Runnable和Callable都运行在与调用线程不同的线程上。但是Callable可以返回值,而Runnable不能。那么这到底适用于什么呢?

可运行的:如果你有一个火灾和忘记任务,那么使用Runnable。将代码放在Runnable中,当run()方法被调用时,就可以执行任务了。调用线程实际上并不关心您何时执行任务。

可调用的:如果你试图从任务中检索一个值,那么使用Callable。可调用本身是做不到的。你将需要一个Future,你将它包裹在你的Callable中,并获得关于Future的价值。get()。在这里,调用线程将被阻塞,直到Future返回结果,然后等待Callable的call()方法执行。

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

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

Callable接口声明了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();
}

综上所述,有几个显著的差异

  1. Runnable对象不返回结果,而Callable对象返回结果。
  2. Runnable对象不能抛出检查异常,而Callable对象可以抛出检查异常 李例外。< / > Runnable接口从Java 1.0开始就有了,而Callable只是引入的

相似之处包括

    实现Runnable或Callable接口的类实例是潜在的 由另一个线程执行
  1. 可调用接口和可运行接口的实例都可以由ExecutorService通过submit()方法执行。
  2. 两者都是功能接口,从Java8开始就可以在Lambda表达式中使用。

ExecutorService接口中的方法是

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

oracle文档中这些接口的用途:

可运行的接口应该由任何类实现,其实例将由Thread接口执行。该类必须定义一个名为run的无参数方法。

可调用的:返回结果并可能抛出异常的任务。实现者定义一个不带参数的方法叫做call。 Callable接口类似于Runnable,因为两者都是为其实例可能由另一个线程执行的类而设计的。但是,Runnable不返回结果,也不能抛出检查异常

其他的差异:

  1. 你可以传递Runnable来创建线程。但是你不能通过传递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();
    }
    
    
    }
    
  2. Use Runnable for fire and forget calls. Use Callable to verify the result.

  3. Callable can be passed to invokeAll method unlike Runnable. Methods invokeAny and invokeAll perform the most commonly useful forms of bulk execution, executing a collection of tasks and then waiting for at least one, or all, to complete

  4. Trivial difference : method name to be implemented => run() for Runnable and call() for Callable.

Callable和Runnable之间的区别如下:

  1. 在JDK 5.0中引入了Callable,而在JDK 1.0中引入了Runnable
  2. 可调用对象有call()方法,而Runnable有run()方法。
  3. 可调用对象有返回值的call方法,而可运行对象有不返回任何值的run方法。
  4. 调用方法可以抛出检查异常,但运行方法不能抛出检查异常。
  5. 可调用的使用submit()方法放入任务队列,可运行的使用execute()方法放入任务队列。
+----------------------------------------+--------------------------------------------------------------------------------------------------+
|              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接口的原因,Runnable接口自Java 1.0以来一直是Java的一部分。# EYZ4

Callable和可运行的都彼此相似,可以在实现线程时使用。在实现可运行的的情况下,你必须实现run ()方法,但在可调用的情况下,你必须实现叫()方法,这两种方法的工作方式相似,但可调用的叫()方法有更多的灵活性。他们之间有一些不同。

可运行的可调用的之间的差异如下——

1) 可运行的run ()方法返回无效,这意味着如果你想让你的线程返回一些你可以进一步使用的东西,那么你有Runnable run()没有选择方法。有一个解决方案可调用的,如果你想返回任何形式的对象然后你应该使用Callable而不是Runnable。可调用接口有方法'call()'返回Object

方法签名- 可运行- > < / p >

public void run(){}

可调用的- - - >

public Object call(){}

2)对于可运行run ()方法,如果出现任何检查异常,则必须需要用试抓块处理,但对于可调用的调用()方法,则必须可以抛出检查异常,如下所示

 public Object call() throws Exception {}

3) 可运行的来自传统的java 1.0版本,但可调用的来自Java 1.5版本和实行框架。

如果你熟悉实行,那么你应该使用使用Callable而不是Runnable

希望你能理解。

当我们使用Executer框架时,可运行的(vs)可调用的就出现了。

ExecutorService是<强> # EYZ0 < / >强的子接口,它接受可运行任务和可调用任务。

早期的多线程可以使用接口Runnable自1.0 < em > < / em >来实现,但这里的问题是在完成线程任务后,我们无法收集线程信息。为了收集数据,我们可以使用静态字段。

使用不同的线程收集每个学生的数据。

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 < em > < / em >,它返回一个结果并可能抛出异常。

  • 单一抽象方法:可调用和可运行接口都有一个抽象方法,这意味着它们可以用于java 8的lambda表达式。

    public interface Runnable {
    public void run();
    }
    
    
    public interface Callable<Object> {
    public Object call() throws Exception;
    }
    

There are a few different ways to delegate tasks for execution to an ExecutorService.

  • execute(Runnable task):void crates new thread but not blocks main thread or caller thread as this method return void.
  • submit(Callable<?>):Future<?>, submit(Runnable):Future<?> crates new thread and blocks main thread when you are using future.get().

Example of using Interfaces Runnable, Callable with Executor framework.

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();
}
}

Java功能接口

它是一种与函数式编程相匹配的接口命名约定

//Runnable
interface Runnable {
void run();
}


//Action - throws exception
interface Action {
void run() throws Exception;
}


//Consumer - consumes a value/values, throws exception
//BiConsumer,
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
//BiFunction, Function3...
public interface Function1<T, R> {
R apply(T t) throws Exception;
}


...


//Executor
public interface Executor {
void execute(Runnable command);
}

[快速闭包命名]

除了所有其他答案:

我们不能传递/使用Callable到单独的线程执行,即Callable只能在执行器框架中使用。 但是,Runnable可以传递给一个单独的线程执行(new thread (new CustomRunnable())),也可以在Executor Framework中使用