一、寫在開頭
在上一篇文章我們寫《Java併發程式設計之執行緒池十八問》的時候,鑑於當時的篇幅已經過長,很多內容就沒有擴充套件了,在這篇文章裡對一些關鍵知識點進行對比補充。
二、Runnable vs Callable
在建立執行緒的時候,一般會選用 Runnable 和 Callable 兩種方式。
【原始碼對比】
Runnable介面
@FunctionalInterface
public interface Runnable {
/**
* 被執行緒執行,沒有返回值也無法丟擲異常
*/
public abstract void run();
}
Callable介面
@FunctionalInterface
public interface Callable<V> {
/**
* 計算結果,或在無法這樣做時丟擲異常。
* @return 計算得出的結果
* @throws 如果無法計算結果,則丟擲異常
*/
V call() throws Exception;
}
- Runnable自 Java 1.0 以來一直存在,Callable在 Java 1.5 時引入;
- Runnable 介面不會返回結果或丟擲檢查異常,Callable 介面可以;
- Callable支援泛型,可定義返回值型別,但一般情況下沒有返回值時,我們推薦使用Runnable介面,使得程式碼更簡潔!
- 工具類 Executors 可以實現將 Runnable 物件轉換成 Callable 物件。(
Executors.callable(Runnable task) 或 Executors.callable(Runnable task, Object result)
)。
三、execute() vs submit()
線上程池中我們有兩種提交任務的方式,分別是 execute() 和 submit(),雖然我們在上一篇文章中都有用到,但是並沒對它們的特點進行總結,這裡做一個對比:
- execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被執行緒池執行成功與否;
- 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()
- isShutDown 當呼叫 shutdown() 或shutdownNow()方法後返回為 true;
- isTerminated 當呼叫 shutdown() 方法後,並且所有提交的任務完成後返回為 true;當呼叫shutdownNow()方法後,成功停止後返回true;
- 當執行緒池任務都正常完成的話,則這兩種方法均為false。
六、結尾彩蛋
如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!