java 線程池封裝及拒絕策略示例詳解
前文
提到線程的使用以及線程間通信方式,通常情況下我們通過new Thread或者new Runnable創(chuàng)建線程,這種情況下,需要開發(fā)者手動管理線程的創(chuàng)建和回收,線程對象沒有復用,大量的線程對象創(chuàng)建與銷毀會引起頻繁GC,那么事否有機制自動進行線程的創(chuàng)建,管理和回收呢?線程池可以實現(xiàn)該能力。
線程池的優(yōu)點:
- 線程池中線程重用,避免線程創(chuàng)建和銷毀帶來的性能開銷
- 能有效控制線程數(shù)量,避免大量線程搶占資源造成阻塞
- 對線程進行簡單管理,提供定時執(zhí)行預計指定間隔執(zhí)行等策略
線程池的封裝實現(xiàn)
在java.util.concurrent包中提供了一系列的工具類以方便開發(fā)者創(chuàng)建和使用線程池,這些類的繼承關(guān)系及說明如下:

| 類名 | 說明 | 備注 |
|---|---|---|
| Executor | Executor接口提供了一種任務(wù)提交后的執(zhí)行機制,包括線程的創(chuàng)建與運行,線程調(diào)度等,通常不直接使用該類 | / |
| ExecutorService | ExecutorService接口,提供了創(chuàng)建,管理,終止Future執(zhí)行的方法,用于跟蹤一個或多個異步任務(wù)的進度,通常不直接使用該類 | / |
| ScheduledExecutorService | ExecutorService的實現(xiàn)接口,提供延時,周期性執(zhí)行Future的能力,同時具備ExecutorService的基礎(chǔ)能力,通常不直接使用該類 | / |
| AbstractExecutorService | AbstractExecutorService是個虛類,對ExecutorService中方法進行了默認實現(xiàn),其提供了newTaskFor函數(shù),用于獲取RunnableFuture對象,該對象實現(xiàn)了submit,invokeAny和invokeAll方法,通常不直接使用該類 | / |
| ThreadPoolExecutor | 通過創(chuàng)建該類對象就可以構(gòu)建一個線程池,通過調(diào)用execute方法可以向該線程池提交任務(wù)。通常情況下,開發(fā)者通過自定義參數(shù),構(gòu)造該類對象就來獲得一個符合業(yè)務(wù)需求的線程池 | / |
| ScheduledThreadPoolExecutor | 通過創(chuàng)建該類對象就可以構(gòu)建一個可以周期性執(zhí)行任務(wù)的線程池,通過調(diào)用schedule,scheduleWithFixedDelay等方法可以向該線程池提交任務(wù)并在指定時間節(jié)點運行。通常情況下,開發(fā)者通過構(gòu)造該類對象就來獲得一個符合業(yè)務(wù)需求的可周期性執(zhí)行任務(wù)的線程池 | / |
由上表可知,對于開發(fā)者而言,通常情況下我們可以通過構(gòu)造ThreadPoolExecutor對象來獲取一個線程池對象,通過其定義的execute方法來向該線程池提交任務(wù)并執(zhí)行,那么怎么創(chuàng)建線程池呢?讓我們一起看下
ThreadPoolExecutor
ThreadPoolExecutor完整參數(shù)的構(gòu)造函數(shù)如下所示:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
從上述代碼可以看出,在構(gòu)建ThreadPoolExecutor時,主要涉及以下參數(shù):
- corePoolSize:核心線程個數(shù),一般情況下可以使用 處理器個數(shù)/2 作為核心線程數(shù)的取值,可以通過Runtime.getRuntime().availableProcessors()來獲取處理器個數(shù)
- maximumPoolSize:最大線程個數(shù),該線程池支持同時存在的最大線程數(shù)量
- keepAliveTime:非核心線程閑置時的超時時長,超過這個時長,非核心線程就會被回收,我們也可以通過allowCoreThreadTimeOut(true)來設(shè)置核心線程閑置時,在超時時間到達后回收
- unit:keepAliveTime的時間單位
- workQueue:線程池中的任務(wù)隊列,當核心線程數(shù)滿或最大線程數(shù)滿時,通過線程池的execute方法提交的Runnable對象存儲在這個參數(shù)中,遵循先進先出原則
- threadFactory:創(chuàng)建線程的工廠 ,用于批量創(chuàng)建線程,統(tǒng)一在創(chuàng)建線程時進行一些初始化設(shè)置,如是否守護線程、線程的優(yōu)先級等。不指定時,默認使用Executors.defaultThreadFactory() 來創(chuàng)建線程,線程具有相同的NORM_PRIORITY優(yōu)先級并且是非守護線程
- handler:任務(wù)拒絕處理策略,當線程數(shù)量等于最大線程數(shù)且等待隊列已滿時,就會采用拒絕處理策略處理新提交的任務(wù),不指定時,默認的處理策略是AbortPolicy,即拋棄該任務(wù)
綜上,我們可以看出創(chuàng)建一個線程池最少需要明確核心線程數(shù),最大線程數(shù),超時時間及單位,等待隊列這五個參數(shù),下面我們創(chuàng)建一個核心線程數(shù)為1,最大線程數(shù)為3,5s超時回收,等待隊列最多能存放5個任務(wù)的線程池,代碼如下:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue<>(5));
隨后我們使用for循環(huán)向該executor中提交任務(wù),代碼如下:
public static void main(String[] args) {
// 創(chuàng)建線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue<>(5));
for (int i=0;i<10;i++) {
int finalI = i;
System.out.println("put runnable "+ finalI +"to executor");
// 向線程池提交任務(wù)
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+",runnable "+ finalI +"start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+",runnable "+ finalI +"executed");
}
});
}
}
輸出如下:

從輸出可以看到,當提交一個任務(wù)到線程池時,其執(zhí)行流程如下:

線程池拒絕策略
線程池拒絕策略有四類,定義在ThreadPoolExecutor中,分別是:
- AbortPolicy:默認拒絕策略,丟棄提交的任務(wù)并拋出RejectedExecutionException,在該異常輸出信息中,可以看到當前線程池狀態(tài)
- DiscardPolicy:丟棄新來的任務(wù),但是不拋出異常
- DiscardOldestPolicy:丟棄隊列頭部的舊任務(wù),然后嘗試重新執(zhí)行,如果再次失敗,重復該過程
- CallerRunsPolicy:由調(diào)用線程處理該任務(wù)
當然,如果上述拒絕策略不能滿足需求,我們也可以自定義異常,實現(xiàn)RejectedExecutionHandler接口,即可創(chuàng)建自己的線程池拒絕策略,下面是使用自定義拒絕策略的示例代碼:
public static void main(String[] args) {
RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("runnable " + r +" in executor "+executor+" is refused");
}
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue<>(5),handler);
for (int i=0;i<10;i++) {
int finalI = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+",runnable "+ finalI +"start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+",runnable "+ finalI +"executed");
}
};
System.out.println("put runnable "+ runnable+" index:"+finalI +" to executor:"+executor);
executor.execute(runnable);
}
}
輸出如下:

任務(wù)隊列
對于線程池而言,任務(wù)隊列需要是BlockingQueue的實現(xiàn)類,BlockingQueue接口的實現(xiàn)類類圖如下:

下面我們針對常用隊列做簡單了解:
ArrayBlockingQueue:ArrayBlockingQueue是基于數(shù)組的阻塞隊列,在其內(nèi)部維護一個定長數(shù)組,所以使用ArrayBlockingQueue時必須指定任務(wù)隊列長度,因為不論對數(shù)據(jù)的寫入或者讀取都使用的是同一個鎖對象,所以沒有實現(xiàn)讀寫分離,同時在創(chuàng)建時我們可以指定鎖內(nèi)部是否采用公平鎖,默認實現(xiàn)是非公平鎖。
非公平鎖與公平鎖
公平鎖:多個任務(wù)阻塞在同一鎖時,等待時長長的優(yōu)先獲取鎖
非公平鎖:多個任務(wù)阻塞在同一鎖時,鎖可獲取時,一起搶鎖,誰先搶到誰先執(zhí)行
LinkedBlockingQueue:LinkedBlockingQueue是基于鏈表的阻塞隊列,在創(chuàng)建時可不指定任務(wù)隊列長度,默認值是Integer.MAX_VALUE,在LinkedBlockingQueue中讀鎖和寫鎖實現(xiàn)了分支,相對ArrayBlockingQueue而言,效率提升明顯。
SynchronousQueue:SynchronousQueue是一個不存儲元素的阻塞隊列,也就是說當需要插入元素時,必須等待上一個元素被移出,否則不能插入,其適用于任務(wù)多但是執(zhí)行比較快的場景。
PriorityBlockingQueue:PriorityBlockingQueue是一個支持指定優(yōu)先即的阻塞隊列,默認初始化長度為11,最大長度為Integer.MAX_VALUE - 8,可以通過讓裝入隊列的對象實現(xiàn)Comparable接口,定義對象排序規(guī)則來指定隊列中元素優(yōu)先級,優(yōu)先級高的元素會被優(yōu)先取出。
DelayQueue:DelayQueue是一個帶有延遲時間的阻塞隊列,隊列中的元素,只有等待延時時間到了才可以被取出,由于其內(nèi)部用PriorityBlockingQueue維護數(shù)據(jù),故其長度與PriorityBlockingQueue一致。一般用于定時調(diào)度類任務(wù)。
下表從一些角度對上述隊列進行了比較:
| 隊列名稱 | 底層數(shù)據(jù)結(jié)構(gòu) | 默認長度 | 最大長度 | 是否讀寫分離 | 適用場景 |
|---|---|---|---|---|---|
| ArrayBlockingQueue | 數(shù)組 | 0 | 開發(fā)者指定大小 | 否 | 任務(wù)數(shù)量較少時使用 |
| LinkedBlockingQueue | 鏈表 | Integer.MAX_VALUE | Integer.MAX_VALUE | 是 | 大量任務(wù)時使用 |
| SynchronousQueue | 公平鎖-隊列/非公平鎖-棧 | 0 | / | 否 | 任務(wù)多但是執(zhí)行速度快的場景 |
| PriorityBlockingQueue | 對象數(shù)組 | 11 | Integer.MAX_VALUE-8 | 否 | 有任務(wù)需要優(yōu)先處理的場景 |
| DelayQueue | 對象數(shù)組 | 11 | Integer.MAX_VALUE-8 | 否 | 定時調(diào)度類場景 |
以上就是java 線程池封裝及拒絕策略示例詳解的詳細內(nèi)容,更多關(guān)于java 線程池封裝拒絕策略的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
介紹Java的大數(shù)類(BigDecimal)和八種舍入模式
在實際應(yīng)用中,需要對更大或者更小的數(shù)進行運算和處理。Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數(shù)進行精確的運算。本文將介紹Java中的大數(shù)類BigDecimal及其八種舍入模式,有需要的可以參考借鑒。2016-08-08
springcloud feign調(diào)其他微服務(wù)時參數(shù)是對象的問題
這篇文章主要介紹了springcloud feign調(diào)其他微服務(wù)時參數(shù)是對象的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Boot配置特定屬性spring.profiles的方法
這篇文章主要介紹了Spring Boot配置特定屬性spring.profiles的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11
SpringBoot使用mybatis-plus分頁查詢無效的問題解決
MyBatis-Plus提供了很多便捷的功能,包括分頁查詢,本文主要介紹了SpringBoot使用mybatis-plus分頁查詢無效的問題解決,具有一定的參考價值,感興趣的可以了解一下2023-12-12
Spring中@Controller和@RestController的區(qū)別詳解
這篇文章主要介紹了Spring中@Controller和@RestController的區(qū)別詳解,@RestController?是?@Controller?和?@ResponseBody?的結(jié)合體,單獨使用?@RestController?的效果與?@Controller?和?@ResponseBody?二者同時使用的效果相同,需要的朋友可以參考下2023-10-10

