線程池的做用
- 減小資源的開銷
減小了每次建立線程、銷燬線程的開銷。
- 提升響應速度
每次請求到來時,因爲線程的建立已經完成,故能夠直接執行任務,所以提升了響應速度。
- 提升線程的可管理性
線程是一種稀缺資源,若不加以限制,不只會佔用大量資源,並且會影響系統的穩定性。
所以,線程池能夠對線程的建立與中止、線程數量等等因素加以控制,使得線程在一種可控的範圍內運行,不只能保證系統穩定運行,並且方便性能調優。
線程池的實現原理
線程池通常由兩種角色構成:多個工做線程 和 一個阻塞隊列。數組
- 工做線程
工做線程是一組已經處在運行中的線程,它們不斷地向阻塞隊列中領取任務執行。
- 阻塞隊列
阻塞隊列用於存儲工做線程來不及處理的任務。當工做線程都在執行任務時,到來的新任務就只能暫時在阻塞隊列中存儲。
ThreadPoolExecutor的使用
建立線程池
經過以下代碼便可建立一個線程池:框架
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue, handler);
- corePoolSize:基本線程數量
它表示你但願線程池達到的一個值。線程池會盡可能把實際線程數量保持在這個值上下。
- maximumPoolSize:最大線程數量
這是線程數量的上界。
若是實際線程數量達到這個值:
- 阻塞隊列未滿:任務存入阻塞隊列等待執行
- 阻塞隊列已滿:調用飽和策略
- keepAliveTime:空閒線程的存活時間
當實際線程數量超過corePoolSize時,若線程空閒的時間超過該值,就會被中止。
PS:當任務不少,且任務執行時間很短的狀況下,能夠將該值調大,提升線程利用率。
- timeUnit:keepAliveTime的單位
- runnableTaskQueue:任務隊列
這是一個存聽任務的阻塞隊列,能夠有以下幾種選擇:
- ArrayBlockingQueue
它是一個由數組實現的阻塞隊列,FIFO。
- LinkedBlockingQueue
它是一個由鏈表實現的阻塞隊列,FIFO。
吞吐量一般要高於ArrayBlockingQueue。
fixedThreadPool使用的阻塞隊列就是它。
它是一個無界隊列。
- SynchronousQueue
它是一個沒有存儲空間的阻塞隊列,任務提交給它以後必需要交給一條工做線程處理;若是當前沒有空閒的工做線程,則當即建立一條新的工做線程。
cachedThreadPool用的阻塞隊列就是它。
它是一個無界隊列。
- PriorityBlockingQueue
它是一個優先權阻塞隊列。
- handler:飽和策略
當實際線程數達到maximumPoolSize,而且阻塞隊列已滿時,就會調用飽和策略。
JDK1.5由四種飽和策略:
- AbortPolicy
默認。直接拋異常。
- CallerRunsPolicy
只用調用者所在的線程執行任務。
- DiscardOldestPolicy
丟棄任務隊列中最久的任務。
- DiscardPolicy
丟棄當前任務。
提交任務
能夠向ThreadPoolExecutor提交兩種任務:Callable和Runnable。異步
- Callable
該類任務有返回結果,能夠拋出異常。
經過submit函數提交,返回Future對象。
可經過get獲取執行結果。
- Runnable
該類任務只執行,沒法獲取返回結果,並在執行過程當中沒法拋異常。
經過execute提交。
關閉線程池
關閉線程池有兩種方式:shutdown和shutdownNow,關閉時,會遍歷全部的線程,調用它們的interrupt函數中斷線程。但這兩種方式對於正在執行的線程處理方式不一樣。函數
- shutdown()
僅中止阻塞隊列中等待的線程,那些正在執行的線程就會讓他們執行結束。
- shutdownNow()
不只會中止阻塞隊列中的線程,並且會中止正在執行的線程。
ThreadPoolExecutor運行機制
當有請求到來時:性能
- 若當前實際線程數量 少於 corePoolSize,即便有空閒線程,也會建立一個新的工做線程;
- 若當前實際線程數量處於corePoolSize和maximumPoolSize之間,而且阻塞隊列沒滿,則任務將被放入阻塞隊列中等待執行;
- 若當前實際線程數量 小於 maximumPoolSize,但阻塞隊列已滿,則直接建立新線程處理任務;
- 若當前實際線程數量已經達到maximumPoolSize,而且阻塞隊列已滿,則使用飽和策略。
設置合理的線程池大小
任務通常可分爲:CPU密集型、IO密集型、混合型,對於不一樣類型的任務須要分配不一樣大小的線程池。spa
- CPU密集型任務
儘可能使用較小的線程池,通常爲CPU核心數+1。
由於CPU密集型任務使得CPU使用率很高,若開過多的線程數,只能增長上下文切換的次數,所以會帶來額外的開銷。
- IO密集型任務
可使用稍大的線程池,通常爲2*CPU核心數。
IO密集型任務CPU使用率並不高,所以可讓CPU在等待IO的時候去處理別的任務,充分利用CPU時間。
- 混合型任務
能夠將任務分紅IO密集型和CPU密集型任務,而後分別用不一樣的線程池去處理。
只要分完以後兩個任務的執行時間相差不大,那麼就會比串行執行來的高效。
由於若是劃分以後兩個任務執行時間相差甚遠,那麼先執行完的任務就要等後執行完的任務,最終的時間仍然取決於後執行完的任務,並且還要加上任務拆分與合併的開銷,得不償失。
Executor兩級調度模型
在HotSpot虛擬機中,Java中的線程將會被一一映射爲操做系統的線程。
在Java虛擬機層面,用戶將多個任務提交給Executor框架,Executor負責分配線程執行它們;
在操做系統層面,操做系統再將這些線程分配給處理器執行。操作系統
Executor結構
Executor框架中的全部類能夠分紅三類:線程
- 任務
任務有兩種類型:Runnable和Callable。
- 任務執行器
Executor框架最核心的接口是Executor,它表示任務的執行器。
Executor的子接口爲ExecutorService。
ExecutorService有兩大實現類:ThreadPoolExecutor和ScheduledThreadPoolExecutor。
- 執行結果
Future接口表示異步的執行結果,它的實現類爲FutureTask。
線程池
Executors工廠類能夠建立四種類型的線程池,經過Executors.newXXX便可建立。3d
1. FixedThreadPoolcode
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
- 它是一種固定大小的線程池;
- corePoolSize和maximunPoolSize都爲用戶設定的線程數量nThreads;
- keepAliveTime爲0,意味着一旦有多餘的空閒線程,就會被當即中止掉;但這裏keepAliveTime無效;
- 阻塞隊列採用了LinkedBlockingQueue,它是一個無界隊列;
- 因爲阻塞隊列是一個無界隊列,所以永遠不可能拒絕任務;
- 因爲採用了無界隊列,實際線程數量將永遠維持在nThreads,所以maximumPoolSize和keepAliveTime將無效。
2. CachedThreadPool
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}
- 它是一個能夠無限擴大的線程池;
- 它比較適合處理執行時間比較小的任務;
- corePoolSize爲0,maximumPoolSize爲無限大,意味着線程數量能夠無限大;
- keepAliveTime爲60S,意味着線程空閒時間超過60S就會被殺死;
- 採用SynchronousQueue裝等待的任務,這個阻塞隊列沒有存儲空間,這意味着只要有請求到來,就必需要找到一條工做線程處理他,若是當前沒有空閒的線程,那麼就會再建立一條新的線程。
3. SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
- 它只會建立一條工做線程處理任務;
- 採用的阻塞隊列爲LinkedBlockingQueue;
4. ScheduledThreadPool
它用來處理延時任務或定時任務。
- 它接收SchduledFutureTask類型的任務,有兩種提交任務的方式:
- scheduledAtFixedRate
- scheduledWithFixedDelay
- time:任務開始的時間
- sequenceNumber:任務的序號
- period:任務執行的時間間隔
- DelayQueue內部封裝了一個PriorityQueue,它會根據time的前後時間排序,若time相同則根據sequenceNumber排序;
- DelayQueue也是一個無界隊列;
- 工做線程會從DelayQueue取已經到期的任務去執行;
- 執行結束後從新設置任務的到期時間,再次放回DelayQueue