1. 什么是線程池
Java中的線程池是運用場景最多的並發框架,幾乎所有需要異步或並發執行任務的程序
都可以使用線程池。在開發過程中,合理地使用線程池能夠帶來3個好處。
第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,
還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用
線程池,必須對其實現原理了如指掌。
java.util.concurrent.Executors提供了一個 java.util.concurrent.Executor接口的實現用於創建線程池
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
如果:T1 + T3 遠大於 T2,則可以采用線程池,以提高服務器性能。
一個線程池包括以下四個基本組成部分:
1、線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩沖機制。
線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
2.線程池作用
線程池是為突然大量爆發的線程設計的,通過有限的幾個固定線程為大量的操作服務,減少了創建和銷毀線程所需的時間,從而提高效率。
如果一個線程的時間非常長,就沒必要用線程池了(不是不能作長時間操作,而是不宜。),況且我們還不能控制線程池中線程的開始、掛起、和中止。
3.線程池四種創建方式
Java通過Executors(jdk1.5並發包)提供四種線程池,分別為:
ScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
FixedThreadPool :
創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。
該方法返回一個固定線程數量的線程池。該線程池中的線程數量始終不變。當有一個新的任務提交時,線程池中若有空閑線程,則立即執行。若沒有,則新的任務會被暫存在一個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務。
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
SingleThreadExecutor:
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
方法返回一個只有一個線程的線程池。若多余一個任務被提交到該線程池,任務會被保存在一個任務隊列中,待線程空閑,按先入先出的順序執行隊列中的任務。
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
CachedThreadPool:
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
該方法返回一個可根據實際情況調整線程數量的線程池。線程池的線程數量不確定,但若有空閑線程可以復用,則會優先使用可復用的線程。若所有線程均在工作,又有新的任務提交,則會創建新的線程處理任務。所有線程在當前任務執行完畢后,將返回線程池進行復用。
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
4.案例演示:
newCachedThreadPool
創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。示例代碼如下:
package com.company.emple4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author shkstart
* @date 2019/6/10- 16:16
*/
public class Test001 {
public static void main(String[] args) {
//1.創建可緩存的線程池,可重復利用
ExecutorService newExecutorService = Executors.newCachedThreadPool();
//創建了10個線程
for (int i = 0; i < 10; i++) {
int temp = i;
newExecutorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("threadName;"+Thread.currentThread().getName()+",i"+temp);
}
});
}
}
}
可以看到本來創建了10個線程池,這里只用了7個,因為newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程
newFixedThreadPool
創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。
package com.company.emple4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author shkstart
* @date 2019/6/10- 16:16
*/
public class Test001 {
public static void main(String[] args) {
//1.創建可固定長度的線程池
ExecutorService newExecutorService = Executors.newFixedThreadPool(3);
//創建了10個線程
for (int i = 0; i < 10; i++) {
int temp = i;
newExecutorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("threadName;"+Thread.currentThread().getName()+",i"+temp);
}
});
}
}
}
可以看到newFixedThreadPool創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。
newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下:
package com.company.emple4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.*;
/**
* @author shkstart
* @date 2019/6/10- 16:16
*/
public class Test001 {
public static void main(String[] args) {
//1.創建可定時線程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("i:" + temp);
}
}, 3, TimeUnit.SECONDS);
}
}
}
表示延遲3秒執行。
newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:
package com.company.emple4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.*;
/**
* @author shkstart
* @date 2019/6/10- 16:16
*/
public class Test001 {
public static void main(String[] args) {
//1.創建單線程
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("index:" + index);
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
newSingleThreadExecutor.shutdown();
}
}
注意: 結果依次輸出,相當於順序執行各個任務。當shutdown時停止線程
5 為什么不建議使用 Executors靜態工廠構建線程池
阿里巴巴Java開發手冊,明確指出不允許使用Executors靜態工廠構建線程池
原因如下:
線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險
說明:Executors返回的線程池對象的弊端如下:
1:FixedThreadPool 和 SingleThreadPool:
允許的請求隊列(底層實現是LinkedBlockingQueue)長度為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM
2:CachedThreadPool 和 ScheduledThreadPool
允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
6.線程池大小與前四個參數
java有預置線程池:newSingleThreadExecutor,newFixedThreadPool,newCacheedThreadPool,newScheduledThreadPool,newWorkStealingPool。如果不適合,還可以使用ThreadPoolExecutor創建自定義線程池。主要構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
我們接下來介紹參數,其中線程池大小與前四個參數有關。
corePoolSize: 核心線程數,會一直存在,除非allowCoreThreadTimeOut設置為true
剛創建線程池,沒有,即不會預先創建。當任務到來,且當前線程沒有超過corePoolSize,就會創建一個新線程執行該任務,即使其他線程是空閑。
不會因為空閑而被釋放,keepAliveTime不適用。
maximumPoolSize:線程池允許的最大線程池數量。如上面情況,如果當前線程超過corePoolSize,先嘗試排隊,如果隊列滿了或者其他情況不能入隊,那么它不會排隊,而是檢查線程數是否達到maximumPoolSize,如果沒有,就創建線程,直到線程數達到maximumPoolSize。
keepAliveTime:空閑線程存活時間,線程數量超過corePoolSize,空閑線程的最大超時時間。當線程池的線程數大於corePoolSize,額外空閑線程的存活時間。如果到了時間,還沒有新任務,就會釋放線程。值為0,表示線程不會超時釋放。
unit:超時時間的單位
workQueue:工作隊列,保存未執行的Runnable 任務
threadFactory:創建線程的工廠類
handler:當線程已滿,工作隊列也滿了的時候,會被調用。被用來實現各種拒絕策略。
BlockingQueue:阻塞隊列。可以使用LinkedBlockingQueue(默認無界)、ArrayBlockingQueue、PriorityBlockingQueue(無界)、SynchronousQueue(沒實際存儲空間)。使用無界隊列,需要注意,線程數最多達到corePoolSize,新任務來只能排隊,maximumPoolSize沒意義。SynchronousQueue只有正好有空閑線程,才會入隊成功,否則總是創建新線程,直到達到maximumPoolSize。
handler:任務拒絕策略。有界隊列,線程數達到maximumPoolSize,隊列滿了,觸發任務拒絕策略。四種處理方式:AbortPolicy(默認,拋出異常),DiscardPolicy(忽略新任務,不拋異常),DiscardOldestPolicy(扔掉等待時間最長,自己排隊),CallerRunsPolicy(任務提交者線程執行任務)
最佳自定義創建線程池,隊列有界,maximumPoolSize有限,使用任務拒絕策略。如果隊列無界,服務不了的任務總是會排隊,消耗內存,甚至引發內存不足異常。如果隊列有界但maximumPoolSize無線,可能會創建過多線程,占內存和CPU。
1.如果當前線程池中的線程數目小於corePoolSize,則每來一個任務就會去創建一個線程去執行。
2.如果當前線程池中線程數大於corePoolSize,則每來一個任務就會添加到緩存隊列。
3.添加成功,則該任務會等待空閑線程將其取出執行。若添加失敗,則會創建新的線程進行執行。
4.如果隊列已滿,則在總線程數小於maximumPoolSize的前提下,則創建新的線程。
5.如果當前線程池中的線程數達到了maximunPoolSize,則再來新的任務,則執行拒絕策略。
6.如果線程池中的線程數量大於 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許為核心池中的線程設置存活時間,那么核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。
---------------------------------------------------