關於《Java併發程式設計之執行緒池十八問》的補充內容

JavaBuild發表於2024-05-31

一、寫在開頭

在上一篇文章我們寫《Java併發程式設計之執行緒池十八問》的時候,鑑於當時的篇幅已經過長,很多內容就沒有擴充套件了,在這篇文章裡對一些關鍵知識點進行對比補充。

二、Runnable vs Callable

在建立執行緒的時候,一般會選用 RunnableCallable 兩種方式。

【原始碼對比】

Runnable介面

@FunctionalInterface
public interface Runnable {
   /**
    * 被執行緒執行,沒有返回值也無法丟擲異常
    */
    public abstract void run();
}

Callable介面

@FunctionalInterface
public interface Callable<V> {
    /**
     * 計算結果,或在無法這樣做時丟擲異常。
     * @return 計算得出的結果
     * @throws 如果無法計算結果,則丟擲異常
     */
    V call() throws Exception;
}
  1. Runnable自 Java 1.0 以來一直存在,Callable在 Java 1.5 時引入;
  2. Runnable 介面不會返回結果或丟擲檢查異常,Callable 介面可以;
  3. Callable支援泛型,可定義返回值型別,但一般情況下沒有返回值時,我們推薦使用Runnable介面,使得程式碼更簡潔!
  4. 工具類 Executors 可以實現將 Runnable 物件轉換成 Callable 物件。(Executors.callable(Runnable task) 或 Executors.callable(Runnable task, Object result))。

三、execute() vs submit()

線上程池中我們有兩種提交任務的方式,分別是 execute()submit(),雖然我們在上一篇文章中都有用到,但是並沒對它們的特點進行總結,這裡做一個對比:

  1. execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被執行緒池執行成功與否;
  2. submit()方法用於提交需要返回值的任務。執行緒池會返回一個 Future 型別的物件,透過這個 Future 物件可以判斷任務是否執行成功,並且可以透過 Future 的 get()方法來獲取返回值。
//這裡使用Executors只是方便測試,正常使用時推薦使用ThreadPoolExecutor!
ExecutorService executorService = Executors.newFixedThreadPool(3);

Future<String> submit = executorService.submit(() -> {
    try {
        Thread.sleep(5000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "javabuild";
});

String s = submit.get();
System.out.println(s);
executorService.shutdown();

輸出:

javabuild

如果一直沒有獲取到返回結果,會報錯,使用get(long timeout,TimeUnit unit)方法的話,如果在 timeout 時間內任務還沒有執行完,就會丟擲 java.util.concurrent.TimeoutException。

四、shutdown() vs shutdownNow()

在JDK 1.8 中,執行緒池的停止一般使用 shutdown()、shutdownNow()這兩種方法。

方法一: shutdown()

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主鎖
    mainLock.lock(); // 加鎖以確保獨佔訪問

    try {
        checkShutdownAccess(); // 檢查是否有關閉的許可權
        advanceRunState(SHUTDOWN); // 將執行器的狀態更新為SHUTDOWN
        interruptIdleWorkers(); // 中斷所有閒置的工作執行緒
        onShutdown(); // ScheduledThreadPoolExecutor中的掛鉤方法,可供子類重寫以進行額外操作
    } finally {
        mainLock.unlock(); // 無論try塊如何退出都要釋放鎖
    }
    tryTerminate(); // 如果條件允許,嘗試終止執行器
}

在shutdown的原始碼中,會啟動一次順序關閉,在這次關閉中,執行器不再接受新任務,但會繼續處理佇列中的已存在任務,當所有任務都完成後,執行緒池中的執行緒會逐漸退出。

方法二: shutdown()

/**
 * 嘗試停止所有正在執行的任務,停止處理等待的任務,
 * 並返回等待處理的任務列表。
 *
 * @return 從未開始執行的任務列表
 */
public List<Runnable> shutdownNow() {
    List<Runnable> tasks; // 用於儲存未執行的任務的列表
    final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主鎖
    mainLock.lock(); // 加鎖以確保獨佔訪問
    try {
        checkShutdownAccess(); // 檢查是否有關閉的許可權
        advanceRunState(STOP); // 將執行器的狀態更新為STOP
        interruptWorkers(); // 中斷所有工作執行緒
        tasks = drainQueue(); // 清空佇列並將結果放入任務列表中
    } finally {
        mainLock.unlock(); // 無論try塊如何退出都要釋放鎖
    }
    tryTerminate(); // 如果條件允許,嘗試終止執行器
    return tasks; // 返回佇列中未被執行的任務列表
}

與shutdown不同的是shutdownNow會嘗試終止所有的正在執行的任務,清空佇列,停止失敗會丟擲異常,並且返回未被執行的任務列表。

五、isTerminated() vs isShutdown()

  1. isShutDown 當呼叫 shutdown() 或shutdownNow()方法後返回為 true;
  2. isTerminated 當呼叫 shutdown() 方法後,並且所有提交的任務完成後返回為 true;當呼叫shutdownNow()方法後,成功停止後返回true;
  3. 當執行緒池任務都正常完成的話,則這兩種方法均為false。

六、結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

相關文章