介紹java 中 Runnable 和 Callable
從java早期開始,多線程已經就是其主要特性之一。Runable接口是表現多線程任務核心接口,Callable是java1.5之後引入的新街口。
本文,我們探讨下這兩個接口之間的差别。
## 執行機制
這兩個接口都代表能被多線程執行的任務,Runable任務可以使用Thread和ExecutorService執行,而Callable隻能使用後者執行。
傳回值
讓我們深入探讨這些接口處理的傳回值。
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對象不包含任何值。
Callable
Callable接口是一個通用接口,包含單個call方法————其傳回泛型類型V:
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());
}
異常處理
下面我們看如何處理任務執行異常情況:
使用Runnable
因為其run方法沒有任何throws子句作為方法簽名規範,無法進一步傳播檢查異常。
使用Callable
Callable的call方法包含“throws Exception"子句,可以很友善進一步傳播檢查異常:
public class FactorialTask implements Callable<Integer> {
// ...
public Integer call() throws InvalidParamaterException {
if(number < 0) {
throw new InvalidParamaterException("Number should be positive");
}
// ...
}
}
使用ExecutorService執行Callable時,異常被收集在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會抛出異常。
總結
本文,我們探讨了Runnable 和 Callable 接口之間的差異,尤其是異常處理在實際項目中非常有用。