- 為什么要有線程池?
- Java是實現和管理線程池有哪些方式?請簡單舉例如何使用。
- 為什么很多公司不允許使用Executors去創建線程池?那么推薦怎么使用呢?
- ThreadPoolExecutor有哪些核心的配置參數?請簡要說明
- ThreadPoolExecutor可以創建哪是哪三種線程池呢?
- 當隊列滿了并且worker的數量達到maxSize的時候,會怎么樣?
- 說說ThreadPoolExecutor有哪些RejectedExecutionHandler策略?默認是什么策略?
- 簡要說下線程池的任務執行機制?execute–>addWorker–>runworker(getTask)
- 線程池中任務是如何提交的?
- 線程池中任務是如何關閉的?
- 在配置線程池的時候需要考慮哪些配置因素?
- 如何監控線程池的狀態?
為什么要有線程池
- 降低資源消耗(線程無限制地創建,然后使用完畢后銷毀)
- 提高響應速度(無須創建線程)
- 提高線程的可管理性
ThreadPoolExecutor例子
- WorkerThread
SimpleThreadPool
- RejectedExecutionHandlerImpl.java
- MyMonitorThread.java
- WorkerPool.java
ThreadPoolExecutor使用詳解
Execute原理
- 線程池首先當前運行的線程數量是否少于corePoolSize。如果是,則創建一個新的工作線程來執行任務。如果都在執行任務,則進入2.
- 判斷BlockingQueue是否已經滿了,倘若還沒有滿,則將線程放入BlockingQueue。否則進入3.
- 如果創建一個新的工作線程將使當前運行的線程數量超過maximumPoolSize,則交給RejectedExecutionHandler來處理任務。
參數
- corePoolSize線程池中的核心線程數,當提交一個任務時,線程池創建一個新線程執行任務,直到當前線程數等于corePoolSize,即使有其他空閑線程能夠執行新來的任務,也會繼續創建線程;如果當前線程數為corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前創建并啟動所有核心線程。
- workQueue用來保存等待被執行的任務的阻塞隊列.在JDK中提供了如下阻塞隊列:具體可以參考JUC集合:BlockQueue詳解
- ArrayBlockingQueue:基于數組結構的有界阻塞隊列,按FIFO排序任務;
- LinkedBlockingQueue:基于鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量通常要高于ArrayBlockingQueue;
- SynchronousQueue:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀態,吞吐量通常要高于LinkedBlockingQueue;
- PriorityBlockingQueue:具有優先級的無界阻塞隊列;
- maximumPoolSize線程池中允許的最大線程數。如果當前阻塞隊列滿了,且繼續提交任務,則創建新的線程執行任務,前提是當前線程數小于maximumPoolSize;當阻塞隊列是無界隊列,則maximumPoolSize則不起作用,因為無法提交至核心線程池的線程會一直持續地放入workQueue.
- keepAliveTime線程空閑時的存活時間,即當線程沒有任務執行時,該線程繼續存活的時間;默認情況下,該參數只在線程數大于corePoolSize時才有用,超過這個時間的空閑線程將被終止;
- unitkeepAliveTime的單位
- threadFactory創建線程的工廠,通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名。默認為DefaultThreadFactory
- handler線程池的飽和策略,當阻塞隊列滿了,且沒有空閑的工作線程,如果繼續提交任務,必須采取一種策略處理該任務,線程池提供了4種策略:
- AbortPolicy:直接拋出異常,默認策略;
- CallerRunsPolicy:用調用者所在的線程來執行任務;
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執行當前任務;
- DiscardPolicy:直接丟棄任務;
三種類型
newFixedThreadPool
- 線程池里的線程數量不超過corePoolSize,這導致了maximumPoolSize和keepAliveTime將會是個無用參數
- 由于使用了無界隊列,所以FixedThreadPool永遠不會拒絕,即飽和策略失效
newSingleThreadExecutor
newCachedThreadPool
- 主線程調用SynchronousQueue的offer()方法放入task,倘若此時線程池中有空閑的線程嘗試讀取SynchronousQueue的task,即調用了SynchronousQueue的poll(),那么主線程將該task交給空閑線程.否則執行(2)
- 當線程池為空或者沒有空閑的線程,則創建新的線程執行任務.
- 執行完任務的線程倘若在60s內仍空閑,則會被終止.因此長時間空閑的CachedThreadPool不會持有任何線程資源.
關閉線程池
關閉方式-shutdown
關閉方式-shutdownNow
ThreadPoolExecutor源碼詳解
幾個關鍵屬性
內部狀態
- RUNNING:-1<<COUNT_BITS,即高3位為111,該狀態的線程池會接收新任務,并處理阻塞隊列中的任務;
- SHUTDOWN:0<<COUNT_BITS,即高3位為000,該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
- STOP:1<<COUNT_BITS,即高3位為001,該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
- TIDYING:2<<COUNT_BITS,即高3位為010,所有的任務都已經終止;
- TERMINATED:3<<COUNT_BITS,即高3位為011,terminated()方法已經執行完成
任務的執行
execute–>addWorker–>runworker(getTask)
execute()方法
ThreadPoolExecutor.execute(task)實現了Executor.execute(task)
- 為什么需要doublecheck線程池的狀態?
addWorker方法
Worker類的runworker方法
- 繼承了AQS類,可以方便的實現工作線程的中止操作;
- 實現了Runnable接口,可以將自身作為一個任務在工作線程中執行;
- 當前提交的任務firstTask作為參數傳入Worker的構造方法;
- 線程啟動之后,通過unlock方法釋放鎖,設置AQS的state為0,表示運行可中斷;
- Worker執行firstTask或從workQueue中獲取任務:進行加鎖操作,保證thread不被其他線程中斷(除非線程池被中斷)檢查線程池狀態,倘若線程池處于中斷狀態,當前線程將中斷。執行beforeExecute執行任務的run方法執行afterExecute方法解鎖操作
通過getTask方法從阻塞隊列中獲取等待的任務,如果隊列中沒有任務,getTask方法會被阻塞并掛起,不會占用cpu資源;
注意這里一段代碼是keepAliveTime起作用的關鍵:
任務的提交
- submit任務,等待線程池execute
- 執行FutureTask類的get方法時,會把主線程封裝成WaitNode節點并保存在waiters鏈表中,并阻塞等待運行結果;
- FutureTask任務執行完成后,通過UNSAFE設置waiters相應的waitNode為null,并通過LockSupport類unpark方法喚醒主線程;
- Callable接口類似于Runnable,只是Runnable沒有返回值。
- Callable任務除了返回正常結果之外,如果發生異常,該異常也會被返回,即Future可以拿到異步執行任務各種結果;
- Future.get方法會導致主線程阻塞,直到Callable任務執行完成;
submit方法
AbstractExecutorService.submit()實現了ExecutorService.submit()可以獲取執行完的返回值,而ThreadPoolExecutor是AbstractExecutorService.submit()的子類,所以submit方法也是ThreadPoolExecutor`的方法。
FutureTask對象
- 內部狀態
- get方法
內部通過awaitDone方法對主線程進行阻塞,具體實現如下:
- 判斷FutureTask當前的state,如果大于COMPLETING,說明任務已經執行完成,則直接返回;
- 如果當前state等于COMPLETING,說明任務已經執行完,這時主線程只需通過yield方法讓出cpu資源,等待state變成NORMAL;
- 通過WaitNode類封裝當前線程,并通過UNSAFE添加到waiters鏈表;
- 最終通過LockSupport的park或parkNanos掛起線程;
- 通過執行Callable任務的call方法;
- 如果call執行成功,則通過set方法保存結果;
- 如果call執行有異常,則通過setException保存異常;
任務的關閉
shutdownNow做的比較絕,它先將線程池狀態設置為STOP,然后拒絕所有提交的任務。最后中斷左右正在運行中的worker,然后清空任務隊列。
為什么線程池不允許使用Executors去創建?推薦方式是什么?
- newFixedThreadPool和newSingleThreadExecutor:主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。
- newCachedThreadPool和newScheduledThreadPool:主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。
推薦方式1
推薦方式2
推薦方式3
- CPU密集型:盡可能少的線程,Ncpu+1
- IO密集型:盡可能多的線程,Ncpu*2,比如數據庫連接池
- 混合型:CPU密集型的任務與IO密集型任務的執行時間差別較小,拆分為兩個線程池;否則沒有必要拆分。
監控線程池的狀態
- getTaskCount()Returnstheapproximatetotalnumberoftasksthathaveeverbeenscheduledforexecution.
- getCompletedTaskCount()Returnstheapproximatetotalnumberoftasksthathavecompletedexecution.返回結果少于getTaskCount()。
- getLargestPoolSize()Returnsthelargestnumberofthreadsthathaveeversimultaneouslybeeninthepool.返回結果小于等于maximumPoolSize
- getPoolSize()Returnsthecurrentnumberofthreadsinthepool.
- getActiveCount()Returnstheapproximatenumberofthreadsthatareactivelyexecutingtasks.
參考文章
- 《Java并發編程藝術》
- https://www.jianshu.com/p/87bff5cc8d8c
- https://blog.csdn.net/programmer_at/article/details/79799267
- https://blog.csdn.net/u013332124/article/details/79587436
- https://www.journaldev.com/1069/threadpoolexecutor-java-thread-pool-example-executorservice
由于問答代碼塊插入受限,部分代碼未完全展示,若有需要可閱讀原文:戳我閱讀原文