在Java中设计并发线程时,使用Runnable接口和Callable接口有什么区别,为什么你会选择其中一个而不是另一个?
当前回答
+----------------------------------------+--------------------------------------------------------------------------------------------------+
| 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的一部分。源
其他回答
Callable和Runnable之间的区别如下:
在JDK 5.0中引入了Callable,而在JDK 1.0中引入了Runnable 可调用对象有call()方法,而Runnable有run()方法。 可调用对象有返回值的call方法,而可运行对象有不返回任何值的run方法。 调用方法可以抛出检查异常,但运行方法不能抛出检查异常。 可调用的使用submit()方法放入任务队列,可运行的使用execute()方法放入任务队列。
我在另一篇博客中发现了这一点,可以更好地解释这些差异:
虽然这两个接口都是由希望在不同的执行线程中执行的类实现的,但这两个接口之间有一些区别:
Callable<V>实例返回类型为V的结果,而Runnable实例则不会。 Callable<V>实例可能会抛出受控异常,而Runnable实例则不会
Java的设计者觉得有必要扩展Runnable接口的功能,但他们不想影响Runnable接口的使用,这可能就是为什么他们在Java 1.5中使用一个名为Callable的单独接口,而不是修改已经存在的Runnable的原因。
Callable需要实现call()方法,而Runnable需要实现run()方法。 Callable可以返回值,但Runnable不能。 Callable可以抛出受控异常,但Runnable不能。 可调用对象可以与ExecutorService#invokeXXX(Collection<?扩展Callable<T>> tasks)方法,但Runnable不能。 可运行的{ 空运行(); } 可调用接口<V> { V call()抛出异常; }
当我们使用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();
}
}
除了所有其他答案:
我们不能传递/使用Callable到单独的线程执行,即Callable只能在执行器框架中使用。 但是,Runnable可以传递给一个单独的线程执行(new thread (new CustomRunnable())),也可以在Executor Framework中使用。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 自动化invokerrequired代码模式
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder