天天看點

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

人物背景:

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

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

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

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

阿珍沒在意又繼續問到:“

Runnable

Callable

兩個接口我總搞混,這個到底有什麼不同?”

面對阿珍的靈魂拷問,老徐淡定自若地說:“

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();
}
           

抛出如下異常:

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

總結

  • Callable的任務執行後可傳回值,Runnable的任務不能傳回值。
  • Callable隻可以通過

    ExecutorService

    啟動,Runnable可以通過

    Thread

    ExecutorService

    啟動。
  • Callable的call()方法可以傳播已檢查異常,Runnable的run()方法不可以。