Runnable与Callable比较-Java快速入门教程
1. 概述
从Java的早期开始,多线程一直是该语言的一个主要方面。Runnable是为表示多线程任务而提供的核心接口,Java 1.5 提供了Callable作为Runnable 的改进版本。
在本教程中,我们将探讨这两个接口的差异和应用。
2. 执行机制
这两个接口都旨在表示可由多个线程运行的任务。我们可以使用Thread类或ExecutorService 运行Runnable任务,而我们只能使用后者运行Callable。
3. 返回值
让我们更深入地了解这些接口如何处理返回值。
3.1. 使用Runnable
Runnable接口是一个函数接口,具有单个run() 方法,该方法不接受任何参数或返回任何值。
这适用于我们不查找线程执行结果的情况,例如传入事件日志记录:
public interface Runnable { public void run(); }
让我们通过一个例子来理解这一点:
public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); } }
在此示例中,线程将仅从队列中读取消息并将其记录在日志文件中。任务不返回任何值。
我们可以使用executorService
启动任务:
public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown(); }
在这种情况下,Future对象将不保存任何值。
3.2.使用Callable
Callable接口是一个泛型接口,其中包含返回泛型值V 的单个call() 方法:
public interface Callable<V> { V call() throws Exception; }
让我们看一下计算一个数字的阶乘:
public class FactorialTask implements Callable<Integer> { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; } }
call() 方法的结果在Future对象中返回:
@Test public void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future<Integer> future = executorService.submit(task); assertEquals(120, future.get().intValue()); }
4. 异常处理
让我们看看它们是否适合异常处理。
4.1. 使用Runnable
由于方法签名没有指定“throws”子句,因此我们无法传播进一步检查的异常。
4.2.使用Callable
Callable 的call() 方法包含 “throwsException” 子句,因此我们可以轻松地进一步传播已检查的异常:
public class FactorialTask implements Callable<Integer> { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... } }
如果使用ExecutorService 运行可调用对象,则会在Future对象中收集异常。我们可以通过调用Future.get() 方法来检查这一点。
这将抛出一个ExecutionException,它包装了原始异常:
@Test(expected = ExecutionException.class) public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); Integer result = future.get().intValue(); }
在上面的测试中,抛出ExecutionException,因为我们传递了一个无效的数字。我们可以在这个异常对象上调用getCause() 方法来获取原始检查的异常。
如果我们不调用Future类的 get()方法,则call()方法抛出的异常将不会报告回来,并且任务仍将标记为已完成:
@Test public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> future = executorService.submit(task); assertEquals(false, future.isDone()); }
即使我们已将参数负值的异常抛出到FactorialCallableTask,上述测试也会成功通过。