開發過程中,合理地使用線程池可以帶來3個好處:
降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
提高響應速度:當任務到達時,任務可以不需要等到線程創建就能立即執行。
提高線程的可管理性:線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。
1 線程池的創建
ThreadPoolExecutor有以下四個構造方法
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,按FIFO原則進行排序
- LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,吞吐量高於ArrayBlockingQueue。靜態工廠方法Excutors.newFixedThreadPool()使用了這個隊列
- SynchronousQueue: 一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量高於LinkedBlockingQueue,靜態工廠方法Excutors.newCachedThreadPool()使用了這個隊列
- PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
- AbortPolicy:直接拋出異常,默認情況下采用這種策略
- CallerRunsPolicy:只用調用者所在線程來運行任務
- DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務
- DiscardPolicy:不處理,丟棄掉
2 提交任務
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
(2) submit方法用於提交一個任務並帶有返回值,這個方法將返回一個Future類型對象。可以通過這個返回對象判斷任務是否執行成功,並且可以通過future.get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成。Future<?> future=threadPoolExecutor.submit(futureTask);
Object value=future.get();
3 關閉線程池
4 合理配置線程池
- 任務的性質:CPU密集型任務、IO密集型任務和混合型任務。
- 任務的優先級:高、中和低。
- 任務的執行時間:長、中和短。
- 任務的依賴性:是否依賴其他系統資源,如數據庫連接。
5 線程池應用示例
1 示例1 驗證shutdown和shutdownNow的區別
static BlockingQueue blockingQueue=new ArrayBlockingQueue<>(10);
static ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(10, 20, 1, TimeUnit.MINUTES, blockingQueue);
/**
* 無返回值的任務
* @author songxu
*
*/
class TaskWithoutResult implements Runnable
{
private int sleepTime=1000;//默認睡眠時間1s
public TaskWithoutResult(int sleepTime)
{
this.sleepTime=sleepTime;
}
@Override
public void run()
{
System.out.println("線程"+Thread.currentThread()+"開始運行");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {//捕捉中斷異常
System.out.println("線程"+Thread.currentThread()+"被中斷");
}
System.out.println("線程"+Thread.currentThread()+"結束運行");
}
}
(3)驗證
/**
* 中斷測試
*/
public static void test1()
{
for(int i=0;i<10;i++)
{
Runnable runnable=new TaskWithoutResult(1000);
threadPoolExecutor.submit(runnable);
}
//threadPoolExecutor.shutdown();//不會觸發中斷
threadPoolExecutor.shutdownNow();//會觸發中斷
}
分別測試shutdown和shutdownNow()方法,結果shutdown()方法的調用並不會引發中斷,而shutdownNow()方法則會引發中斷。這也正驗證前面所說的,shutdown方法只是發出了停止信號,等所有線程執行完畢會關閉線程池;而shutdownNow則是立即停止所有任務。
2 示例2 驗證線程池的擴容
static BlockingQueue blockingQueue=new ArrayBlockingQueue<>(1);
static ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(10, 20, 1, TimeUnit.MINUTES, blockingQueue);
class TaskBusyWithoutResult implements Runnable
{
public TaskBusyWithoutResult()
{
}
@Override
public void run()
{
System.out.println("線程"+Thread.currentThread()+"開始運行");
int i=10000*10000;
while(i>0)
{
i--;
}
System.out.println("線程"+Thread.currentThread()+"運行結束");
}
}
(3)測試驗證,向線程池提交20個任務/**
* 擴容測試
*/
public static void test2()
{
for(int i=0;i<20;i++)
{
Runnable runnable=new TaskBusyWithoutResult();
threadPoolExecutor.submit(runnable);
}
}
(4)驗證結果