老徐和阿珍的故事:Runnable和Callable有什麼不同?

萬貓學社發表於2022-03-24

人物背景

老徐,男,本名徐福貴,從事Java相關研發工作多年,職場老油條,摸魚小能手,雖然歲數不大但長的比較著急,人稱老徐。據說之前炒某幣敗光了所有家產,甚至現在還有欠債。

阿珍,女,本名陳家珍,剛剛入職不久的實習生,雖然是職場菜鳥但聰明好學。據說是學校的四大校花之一,追求她的人從旺角排到了銅鑼灣,不過至今還單身。

阿珍探出頭看了看老徐的螢幕,全部都是綠色的曲線圖,好奇地問:“老徐,你看的這是什麼?”老徐看的太入神,轉過頭才發現阿珍,尬尷地笑了笑說:“我就是看看最近的行情。”老徐立馬切換了視窗。

阿珍沒在意又繼續問到:“RunnableCallable兩個介面我總搞混,這個到底有什麼不同?”

面對阿珍的靈魂拷問,老徐淡定自若地說:“Runnable是用於提供多執行緒任務支援的核心介面,Callable是在Java 1.5中新增的Runnable的改進版本。”

“在聊它們不同之前,我們先分別瞭解一下兩個介面。”老徐一邊說著,一邊開啟了原始碼:

Runnable介面

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable介面是一個函式式介面,它只有一個run()方法,不接受任何引數,也不返回任何值。由於方法簽名沒有指定throws子句,因此無法進一步傳播已檢查的異常。它適用於我們不使用執行緒執行結果的情況,例如,非同步列印日誌:

package one.more;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingTask implements Runnable {

    private static Logger logger = LoggerFactory.getLogger(LoggingTask.class);

    private String name;

    public LoggingTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        logger.info("{}說:你好!", this.name);
    }
}

在上面例中,根據name引數把資訊記錄在日誌檔案中,沒有返回值。我們可以通過Thread啟動,比如:

public static void main(String[] args) {
    String name = "萬貓學社";
    Thread thread = new Thread(new LoggingTask(name));
    thread.start();;
}

我們也可以通過ExecutorService啟動,比如:

public static void main(String[] args) {
    String name = "萬貓學社";
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.execute(new LoggingTask(name));
    executorService.shutdown();
}

Callable介面

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable介面也是一個函式式介面,它只有一個call()方法,不接受任何引數,返回一個泛型值V,在方法簽名上包含throws Exception子句,因此我們可以很容易地進一步傳播已檢查異常。它適用於我們使用執行緒執行結果的情況,例如,非同步計算階乘:

package one.more;

import java.util.concurrent.Callable;

public class FactorialTask implements Callable<Integer> {

    private int n;

    public FactorialTask(int n) {
        this.n = n;
    }

    @Override
    public Integer call() throws IllegalArgumentException {
        int fact = 1;
        if (n < 0) {
            throw new IllegalArgumentException("必須大於等於零");
        }
        for (int i = n; i > 1; i--) {
            fact = fact * i;
        }
        return fact;
    }
}

在上面例中,根據n引數計算它的階乘,並可以返回計算結結果。我們只能通過ExecutorService啟動,比如:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Future<Integer> future = executorService.submit(new FactorialTask(5));
    System.out.println(future.get());
    executorService.shutdown();
}

call()方法的結果可以通過Future物件獲取到,如果在呼叫Future物件的get()方法時,call()方法出現了異常,異常會被繼續傳遞,比如:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Future<Integer> future = executorService.submit(new FactorialTask(-1));
    System.out.println(future.get());
    executorService.shutdown();
}

丟擲如下異常:

萬貓學社.png

老徐回頭看看了阿珍,說:“這回你知道有什麼不同了吧!”阿珍一頭霧水地說:“資訊量有點大呀,可以給我總結一下嗎?”“當然可以。”老徐回答。

總結

Runnable和Callable的不同:

  • Callable的任務執行後可返回值,Runnable的任務不能返回值。
  • Callable只可以通過ExecutorService啟動,Runnable可以通過ThreadExecutorService啟動。
  • Callable的call()方法可以傳播已檢查異常,Runnable的run()方法不可以。

最後,謝謝你這麼帥,還給我點贊關注

微信公眾號:萬貓學社

微信掃描二維碼

關注後回覆「電子書」

獲取12本Java必讀技術書籍

老徐和阿珍的故事:Runnable和Callable有什麼不同?

相關文章