Java 中執行緒池的 7 種建立方式!
在 Java 語言中,併發程式設計都是通過建立執行緒池來實現的,而執行緒池的建立方式也有很多種,每種執行緒池的建立方式都對應了不同的使用場景,總體來說執行緒池的建立可以分為以下兩類:
- 通過 ThreadPoolExecutor 手動建立執行緒池。
- 通過 Executors 執行器自動建立執行緒池。
而以上兩類建立執行緒池的方式,又有 7 種具體實現方法,這 7 種實現方法分別是:
- Executors.newFixedThreadPool:建立一個固定大小的執行緒池,可控制併發的執行緒數,超出的執行緒會在佇列中等待。
- Executors.newCachedThreadPool:建立一個可快取的執行緒池,若執行緒數超過處理所需,快取一段時間後會回收,若執行緒數不夠,則新建執行緒。
- Executors.newSingleThreadExecutor:建立單個執行緒數的執行緒池,它可以保證先進先出的執行順序。
- Executors.newScheduledThreadPool:建立一個可以執行延遲任務的執行緒池。
- Executors.newSingleThreadScheduledExecutor:建立一個單執行緒的可以執行延遲任務的執行緒池。
- Executors.newWorkStealingPool:建立一個搶佔式執行的執行緒池(任務執行順序不確定)【JDK 1.8 新增】。
- ThreadPoolExecutor:手動建立執行緒池的方式,它建立時最多可以設定 7 個引數。
接下來我們分別來看這 7 種執行緒池的具體使用。
1.FixedThreadPool
建立一個固定大小的執行緒池,可控制併發執行緒數。
使用 FixedThreadPool 建立 2 個固定大小的執行緒池,具體實現程式碼如下:
public static void fixedThreadPool() { // 建立 2 個執行緒的執行緒池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("任務被執行,執行緒:" + Thread.currentThread().getName()); } }; // 執行緒池執行任務(一次新增 4 個任務) // 執行任務的方法有兩種:submit 和 execute threadPool.submit(runnable); // 執行方式 1:submit threadPool.execute(runnable); // 執行方式 2:execute threadPool.execute(runnable); threadPool.execute(runnable); }
以上程式的執行結果如下圖所示:
如果覺得以上方法比較繁瑣,還用使用以下簡單的方式來實現執行緒池的建立和使用:
public static void fixedThreadPool() {
// 建立執行緒池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 執行任務
threadPool.execute(() -> {
System.out.println("任務被執行,執行緒:" + Thread.currentThread().getName());
});
}
2.CachedThreadPool
建立一個可快取的執行緒池,若執行緒數超過任務所需,那麼多餘的執行緒會被快取一段時間後才被回收,若執行緒數不夠,則會新建執行緒。
CachedThreadPool 使用示例如下:
public static void cachedThreadPool() {
// 建立執行緒池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 執行任務
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println("任務被執行,執行緒:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
});
}
}
以上程式的執行結果如下圖所示:
從上述結果可以看出,執行緒池建立了 10 個執行緒來執行相應的任務。
使用場景
CachedThreadPool 是根據短時間的任務量來決定建立的執行緒數量的,所以它適合短時間內有突發大量任務的處理場景。
3.SingleThreadExecutor
建立單個執行緒的執行緒池,它可以保證先進先出的執行順序。
SingleThreadExecutor 使用示例如下:
public static void singleThreadExecutor() {
// 建立執行緒池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 執行任務
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + ":任務被執行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
});
}
}
以上程式的執行結果如下圖所示:
單個執行緒的執行緒池有什麼意義?
單個執行緒的執行緒池相比於執行緒來說,它的優點有以下 2 個:
- 可以複用執行緒:即使是單個執行緒池,也可以複用執行緒。
- 提供了任務管理功能:單個執行緒池也擁有任務佇列,在任務佇列可以儲存多個任務,這是執行緒無法實現的,並且當任務佇列滿了之後,可以執行拒絕策略,這些都是執行緒不具備的。
4.ScheduledThreadPool
建立一個可以執行延遲任務的執行緒池。
使用示例如下:
public static void scheduledThreadPool() {
// 建立執行緒池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
// 新增定時執行任務(1s 後執行)
System.out.println("新增任務,時間:" + new Date());
threadPool.schedule(() -> {
System.out.println("任務被執行,時間:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 1, TimeUnit.SECONDS);
}
以上程式的執行結果如下圖所示:
從上述結果可以看出,任務在 1 秒之後被執行了,實現了延遲 1s 再執行任務。
5.SingleThreadScheduledExecutor
建立一個單執行緒的可以執行延遲任務的執行緒池,此執行緒池可以看作是 ScheduledThreadPool 的單執行緒池版本。
它的使用示例如下:
public static void SingleThreadScheduledExecutor() {
// 建立執行緒池
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 新增定時執行任務(2s 後執行)
System.out.println("新增任務,時間:" + new Date());
threadPool.schedule(() -> {
System.out.println("任務被執行,時間:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 2, TimeUnit.SECONDS);
}
以上程式的執行結果如下圖所示:
從上述結果可以看出,任務在 2 秒之後被執行了。
6.newWorkStealingPool
建立一個搶佔式執行的執行緒池(任務執行順序不確定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程式中才能使用。
newWorkStealingPool 使用示例如下:
public static void workStealingPool() {
// 建立執行緒池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 執行任務
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被執行,執行緒名:" + Thread.currentThread().getName());
});
}
// 確保任務執行完成
while (!threadPool.isTerminated()) {
}
}
以上程式的執行結果如下圖所示:
從上述結果可以看出,任務的執行順序是不確定的,因為它是搶佔式執行的。
7.ThreadPoolExecutor
ThreadPoolExecutor 是最原始、也是最推薦的手動建立執行緒池的方式,它在建立時最多提供 7 個引數可供設定。
ThreadPoolExecutor 使用示例如下:
public static void myThreadPoolExecutor() {
// 建立執行緒池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
// 執行任務
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被執行,執行緒名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
以上程式的執行結果如下圖所示:
ThreadPoolExecutor 相比於其他建立執行緒池的優勢在於,它可以通過引數來控制最大任務數和拒絕策略,讓執行緒池的執行更加透明和可控,所以在阿里巴巴《Java開發手冊》是這樣規定的:
【強制要求】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
說明:Executors 返回的執行緒池物件的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool:允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM。
總結
執行緒池的建立方式總共有以下 7 種:
- Executors.newFixedThreadPool:建立一個固定大小的執行緒池,可控制併發的執行緒數,超出的執行緒會在佇列中等待。
- Executors.newCachedThreadPool:建立一個可快取的執行緒池,若執行緒數超過處理所需,快取一段時間後會回收,若執行緒數不夠,則新建執行緒。
- Executors.newSingleThreadExecutor:建立單個執行緒數的執行緒池,它可以保證先進先出的執行順序。
- Executors.newScheduledThreadPool:建立一個可以執行延遲任務的執行緒池。
- Executors.newSingleThreadScheduledExecutor:建立一個單執行緒的可以執行延遲任務的執行緒池。
- Executors.newWorkStealingPool:建立一個搶佔式執行的執行緒池(任務執行順序不確定)【JDK 1.8 新增】。
- ThreadPoolExecutor:手動建立執行緒池的方式,它建立時最多可以設定 7 個引數。
而執行緒池的建立推薦使用最後一種 ThreadPoolExecutor 的方式來建立,因為使用它可以明確執行緒池的執行規則,規避資源耗盡的風險。
關注下面二維碼,訂閱更多精彩內容。是非審之於己,譭譽聽之於人,得失安之於數。
公眾號:Java面試真題解析
關注公眾號(加好友):
作者:
王磊的部落格
出處:
http://vipstone.cnblogs.com/