上一篇文章中我們自己寫了一個簡單的線程池。
這一篇文章我們來了解一下java爲我們提供的線程池實現—— ExecutorService接口
它位於jdk的java.util.concurrent包下。
JDK提供了這麼兩個類來實現這個接口:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
我們這篇文章只介紹一下ThreadPoolExecutor類(ScheduledThreadPoolExecutor類類似,多加入了計劃任務功能。)
我們首先看看怎麼用ThreadPoolExecutor類初始化一個線程池:
//初始化一個線程池
//核心線程數
int corePoolSize = 5;
//最大線程數
int maxPoolSize = 10;
//空閒線程最大存活時間
long keepAliveTime = 5000;
//任務隊列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5);
ExecutorService threadPoolExecutor =
new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
queue
);
這個類完全實現了一個類似於我們上一篇文章中實現的線程池,它包含以下幾個屬性:
1. corePoolSize
核心線程數:即使沒有任何任務過來,線程池裏面也會有保持的最基本線程數。
2. maximumPoolSize
最大線程數(即使任務特別多,線程池裏的線程數也不會超過它)
3. keepAliveTime
空閒線程最大存活時間
4. blockingQueue
任務隊列,用來存放待處理的任務。我們在這個系列的第一篇文章中就介紹過了它。
可以選擇以下幾個阻塞隊列。
- ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
- LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。
- SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue。
- PriorityBlockingQueue:一個具有優先級得無限阻塞隊列。
那麼整個類的結構就如下圖所示:
要使用這個線程池的話,可以使用它提供給我們的如下方法:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
execute()和submit()可以向這個線程池提交單個任務。他們的區別是:
使用execute提交任務,但是execute方法沒有返回值,所以無法判斷任務知否被線程池執行成功。
使用submit 方法來提交任務,它會返回一個future對象,那麼我們可以通過這個future對象來判斷任務是否執行成功
invokeAll可以直接把一個List類型的任務列表一次性的提交給線程池執行。
==================================================================================
那麼接下來我們就用 ThreadPoolExecutor 來創建一個線程池,改寫一下我們上一篇文章的例子:
public class ProblemCreater {
public static void main(String[] args) throws Exception {
//初始化線程池
//核心線程數
int corePoolSize = 5;
//最大線程數
int maxPoolSize = 10;
long keepAliveTime = 5000;
ExecutorService threadPoolExecutor =
new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5)
);
//生成者不斷產生任務
for(int i=1;i<10;i++){
//定義一個新的任務
Runnable task = new Runnable(){
public void run(){
Random random = new Random();
//隨機一個數字模擬需要解決的時間
int randomTime = Math.abs(random.nextInt())%20;
try {
Thread.sleep(randomTime*1000);
System.out.println("任務完成,花費時間爲:"+randomTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//將問題插入到線程池任務隊列中
threadPoolExecutor.execute(task);
System.out.println("插入新的任務"+i);
}
}
}
我們再回頭看看這個 ThreadPoolExecutor 的初始化,我們需要給它傳遞5個參數。核心線程數,最大線程數,線程存活時間,時間單位,還有阻塞隊列的類型。
這個過程還是比較繁瑣的。其實Java幫我們簡化了這個過程,我們可以根據不同的情景,直接用一行代碼創建一個合適的線程池。
實現這個功能的就是 java.util.concurrent.Executors類。我們在下一篇文章中再詳細介紹這個類的用法。