在使用Java線程池實現各種的需求過程中,很是能體會線程池的好處。但是隨着需求增加,發現Java線程池自帶的集中模式也有點不太夠用。所以又想自己根據現有的API進行拓展的想法。
Java線程池執行task的流程圖如下:
在是否創建新的線程池邏輯中,只有當核心線程數未滿和任務隊列已經滿了兩種情況,但是在性能測試過程中,經常會批量初始化很多數據,這個時候如果使用異步進行初始化,就需要一個相當大的等待隊列長度,而通常線程池使用核心線程數和最大線程數來控制線程池的活躍線程數量。無法實現動態根據等待隊列中的數量多少靈活增加活躍線程數來提升異步任務的處理能力,也無法動態減低,減少線程池活躍線程,降低資源消耗。
這個問題的主要癥結在於參數corePoolSize
設置之後,就無法通過當前的策略進行自動調整。如果使用cache線程池,那麼等待隊列又無法容納大量等待任務。
翻看源碼得到java.util.concurrent.ThreadPoolExecutor#setCorePoolSize
這個API可以在線程池啓動之後重新設置corePoolSize
,通過這個API基本就是實現主動調整活躍線程數數量,實現上面提到的需求。
首先確認一個增加和減少的策略,我是這麼設計的:如果等待隊列超過100,就增加1個活躍線程(corePoolSize),如果等待隊列長度爲零,就減少1個活躍線程(corePoolSize)。當然增加減少都在一個範圍內。
其次要解決檢測的策略,我一開始設想就是在添加任務的時候進行檢測,發現容易發生,任務隊列開始超過閾值之後,進來一個任務就創建了一個線程,一下子就到最大值了,缺少緩存。後來決定使用定時機制進行檢測。最終決定在daemon
線程中實現。由於daemon特殊機制使用了1s作爲間隔,所以單獨設置了一個5s的線程池檢測機制。
/**
* 執行daemon線程,保障main方法結束後關閉線程池
* @return
*/
static boolean daemon() {
def set = DaemonState.getAndSet(true)
if (set) return
def thread = new Thread(new Runnable() {
@Override
void run() {
SourceCode.noError {
while (checkMain()) {
SourceCode.sleep(1.0)
def pool = getFunPool()
if (SourceCode.getMark() - poolMark > 5) {
poolMark = SourceCode.getMark()
def size = pool.getQueue().size()
def corePoolSize = pool.getCorePoolSize()
if (size > MAX_ACCEPT_WAIT_TASK && corePoolSize < POOL_MAX) {
pool.setCorePoolSize(corePoolSize + 1)
log.info("線程池自增" + pool.getCorePoolSize())
}
if (size == 0 && corePoolSize > POOL_SIZE) {
pool.setCorePoolSize(corePoolSize - 1)
log.info("線程池自減" + pool.getCorePoolSize())
}
}
ASYNC_QPS.times {executeCacheSync()}
}
waitAsyncIdle()
}
ThreadPoolUtil.shutPool()
}
})
thread.setDaemon(true)
thread.setName("Daemon")
thread.start()
}
如果是在Springboot
項目中的話,daemon
線程會很快結束,所以需要寫成一個scheduled
定時任務,這裏代碼相同就不多贅述了。
在即將寫完本篇文章的時候發現一個另外的API:java.util.concurrent.ThreadPoolExecutor#addWorker
,第二個參數註釋如下:if true use corePoolSize as bound, else maximumPoolSize. (A boolean indicator is used here rather than a value to ensure reads of fresh values after checking other pool state).
也就是說第二個參數爲false的話就使用的最大值的線程,在execute()
源碼中有註釋:
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
第二步,如果添加隊列失敗或者重新檢測沒通過,則會創建新線程。其中調用的方法如下:addWorker(command, false)
,說明這個API的功能應該就是直接創建線程執行任務的方法。可惜private,直接調用的話,不太優雅。暫時放棄了。