在Java中设计并发线程时,使用Runnable接口和Callable接口有什么区别,为什么你会选择其中一个而不是另一个?
当前回答
Callable需要实现call()方法,而Runnable需要实现run()方法。 Callable可以返回值,但Runnable不能。 Callable可以抛出受控异常,但Runnable不能。 可调用对象可以与ExecutorService#invokeXXX(Collection<?扩展Callable<T>> tasks)方法,但Runnable不能。 可运行的{ 空运行(); } 可调用接口<V> { V call()抛出异常; }
其他回答
让我们看看在哪里可以使用Runnable和Callable。
Runnable和Callable都运行在与调用线程不同的线程上。但是Callable可以返回值,而Runnable不能。那么这到底适用于什么呢?
Runnable:如果你有一个火和忘记任务,那么使用Runnable。将代码放在Runnable中,当run()方法被调用时,就可以执行任务了。调用线程实际上并不关心您何时执行任务。
Callable:如果您试图从任务中检索值,则使用Callable。可调用本身是做不到的。你将需要一个Future,你将它包裹在你的Callable中,并获得关于Future的价值。get()。在这里,调用线程将被阻塞,直到Future返回结果,然后等待Callable的call()方法执行。
因此,考虑一个到目标类的接口,其中定义了Runnable和Callable包装方法。调用类会随机调用你的接口方法,不知道哪个是Runnable,哪个是Callable。Runnable方法将异步执行,直到Callable方法被调用。这里调用类的线程将阻塞,因为您正在从目标类中检索值。
注意:在目标类内部,您可以在单个线程执行器上调用Callable和Runnable,使此机制类似于串行调度队列。因此,只要调用者调用你的Runnable包装方法,调用线程就会非常快地执行而不会阻塞。一旦它调用了一个包装在Future方法中的Callable,它将不得不阻塞,直到所有其他排队的项都被执行。只有这样,该方法才会返回值。这是一种同步机制。
我在另一篇博客中发现了这一点,可以更好地解释这些差异:
虽然这两个接口都是由希望在不同的执行线程中执行的类实现的,但这两个接口之间有一些区别:
Callable<V>实例返回类型为V的结果,而Runnable实例则不会。 Callable<V>实例可能会抛出受控异常,而Runnable实例则不会
Java的设计者觉得有必要扩展Runnable接口的功能,但他们不想影响Runnable接口的使用,这可能就是为什么他们在Java 1.5中使用一个名为Callable的单独接口,而不是修改已经存在的Runnable的原因。
当我们使用Executer框架时,Runnable (vs) Callable就出现了。
ExecutorService是Executor的子接口,它接受可运行任务和可调用任务。
早期的多线程可以使用Interface RunnableSince 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>Since 1.5,它返回一个结果并可能引发异常。
单一抽象方法:可调用接口和可运行接口都有一个单一的抽象方法,这意味着它们可以在java 8的lambda表达式中使用。 可运行的{ 公共无效运行(); } 可调用的<对象> { 公共对象调用()抛出异常; }
有几种不同的方法可以将任务委托给ExecutorService执行。
execute(可运行任务):void将新线程装箱,但不会阻塞主线程或调用线程,因为此方法返回void。 未来提交(可调用< ? >):< ?未来>,提交(Runnable): < ?当你使用future.get()时,>板条箱新线程并阻塞主线程。
在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();
}
}
可调用接口声明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();
}
综上所述,有几个显著的差异
Runnable对象不返回结果,而Callable对象返回结果。 Runnable对象不能抛出检查异常,而Callable对象可以抛出检查异常 例外。 Runnable接口从Java 1.0开始就已经存在了,而Callable只是被引入的 在Java 1.5中。
相似之处包括
实现Runnable或Callable接口的类实例是潜在的 由另一个线程执行。 可调用接口和可运行接口的实例都可以由ExecutorService通过submit()方法执行。 两者都是功能接口,从Java8开始就可以在Lambda表达式中使用。
ExecutorService接口中的方法是
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
正如这里已经提到的,Callable是一个相对较新的接口,它是作为并发包的一部分引入的。Callable和Runnable都可以与执行器一起使用。Thread类(实现Runnable本身)只支持Runnable。
您仍然可以将Runnable与执行程序一起使用。Callable的优点是,你可以将它发送给executor,并立即得到Future结果,当执行完成时,Future结果将被更新。Runnable也可以实现同样的功能,但在这种情况下,您必须自己管理结果。例如,您可以创建保存所有结果的结果队列。其他线程可以等待这个队列并处理到达的结果。
推荐文章
- Eclipse调试器总是阻塞在ThreadPoolExecutor上,没有任何明显的异常,为什么?
- Java生成两个给定值之间的随机数
- 如何有效地从数组列表或字符串数组中删除所有空元素?
- 比较JUnit断言中的数组,简洁的内置方式?
- codestyle;把javadoc放在注释之前还是之后?
- 如何在Spring中定义List bean ?
- 将Set<T>转换为List<T>的最简洁的方法
- 在JavaScript中,什么相当于Java的Thread.sleep() ?
- 使用Java重命名文件
- URL从Java中的类路径加载资源
- .toArray(new MyClass[0]) or .toArray(new MyClass[myList.size()])?
- Hibernate中不同的保存方法之间有什么区别?
- Java 8流和数组操作
- 如何获得GDB中所有线程的回溯?
- Java Regex捕获组