天天看點

介紹java 中 Runnable 和 Callable

介紹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 接口之間的差異,尤其是異常處理在實際項目中非常有用。

繼續閱讀