線程模型
線程模型分爲兩類,用戶級線程(ULT)和內核級線程(KLT)
-
用戶級線程(ULT):user level threads,系統內核對ULT無感知,線程的創建和調度都由用戶級APP進程管理;即APP自行管理的線程,就是用戶級線程
-
內核級線程(KLT):kernel level threads,線程的創建,調度和切換上下文等,都由操作系統內核管理
如圖所示,ULT模型中,每個進程創建的線程,均由自身進程維護管理,而KLT模型中,進程創建的線程,由系統內核進行維護管理。
在執行的區別之上,ULT和KLT最大的區別就是:ULT線程的調度不需要內核直接參與,控制簡單,創建和銷燬線程、線程切換代價等線程管理的開銷比內核線程少得多。但是其資源調度按照進程進行,不能利用系統的多核處理,多個處理器下,同一個進程中的線程只能在同一個處理器下分時複用;KLT由內核進行調度,當有多個處理器時,一個進程的多個線程可以同時執行,但是KLT線程的創建和切換等開銷很大。
Java線程是依賴於系統內核,通過JVM調用系統庫創建內核線程。Java的Thread和內核線程呈 1:1映射關係。by the way,這也是爲什麼我們需要線程池進行線程池化管理的原因。
線程池
基礎概念
線程是程序運行的載體,說白了就是擁有cpu多長時間的執行權。而由於java線程是內核級線程帶來的昂貴開銷成本,在平時運用場景中我們也經常使用線程池對線程進行池化管理。線程池能爲我們帶來3大好處:
- 複用已創建的線程,降低創建銷燬線程等帶來的資源消耗
- 提高響應速度。線程早已創建好,不需要在等待線程創建就可以直接使用
- 可管理線程。由於線程是稀有資源,不能無限創建,因此用線程池能對線程進行統一規劃,監控和分配
在線程池中,有幾項概念最爲重要:核心線程數,最大線程數,等待隊列,飽和策略。它們的執行過程如下圖。
爲加強記憶,可以用工廠幹活的方式理解。把核心線程數看作是正式工,當來了一堆任務的時候,正式工陸續開始幹活,當任務增多的時候,正式工們手頭上的任務還沒幹完,於是先放阻塞隊列等待待會兒處理;後來任務量暴漲,阻塞隊列也滿了,於是就請一些臨時工來幹活,於是,正式工數+臨時工數 = 最大線程數;再後來線程數持續增長,最大線程數也滿了,只能做一些飽和策略限制任務的提交了。
線程池的使用
我們可以通過new 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)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中的參數需要說明下:
corePoolSize:核心線程數。線程池在最初階段,池中線程數爲0,當接收到一個任務後,就創建一個線程。即使之前的線程執行完畢空閒,新任務提交過來,依然會創建線程。直到創建線程到核心線程數。如果調用了prestartAllCoreThreads方法,那麼線程池會預先創建好線程。
BlockingQueue: ⽤於保存等待執⾏的任務的阻塞隊列,可參考JUC的7種阻塞隊列,這裏不再贅述
maximumPoolSize:線程池允許創建的最⼤線程數。如果隊列滿了,並且已創建的線程數⼩於最⼤線程數,則線程池會再創建新的線程執⾏任務
TimeUnit:線程活動保持時間的單位
keepAliveTime:線程活動保持時間。當線程池中創建的線程數量超過核心線程數,且有線程空閒超過該時間,空閒線程將會被回收至核心線程數大小。當設置爲0,即只要線程空閒就立即回收
ThreadFactory:⽤於設置創建線程的⼯⼚,可以通過線程⼯⼚給每個創建出來的線程設置更有意義的名字。附錄附上線程工廠的用法例子。
RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取⼀種策略處理提交的新任務。這個策略默認情況下是AbortPolicy。在JDK1.5中Java線程池框架提供了以下4種策略。
-
AbortPolicy:直接拋出異常。
-
CallerRunsPolicy:當任務添加到線程池中被拒絕時,會在線程池當前正在運行的Thread線程池中處理被拒絕的任務。
-
DiscardOldestPolicy:線程池會放棄等待隊列中最舊的未處理任務,然後將被拒絕的任務添加到等待隊列中。
-
DiscardPolicy:不處理,丟棄掉。
當然也可以自定義,在附錄舉了一個demo可參考。
舉上一個較爲完整的線程池創建示例
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(5);
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, workQueue, new ThreadPoolExecutor.AbortPolicy());
線程池的工作狀態
線程池共有5種生命狀態:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
-
RUNNING:接受新任務並處理排隊的任務
-
SHUTDOWN:不接受新任務,但處理排隊的任務
-
STOP:不接受新任務,不處理排隊的任務,中斷正在進行的任務
-
TIDYING:所有任務都已終止,workerCount爲零,將運行terminate()鉤子方法
-
TERMINATED:線程池終止
線程池提交任務的方式有兩種,分別爲execute()和 submit()⽅法。
execute()⽅法⽤於提交不需要返回值的任務,⽆法判斷任務是否被線程池執⾏成功。
submit()⽅法⽤於提交需要返回值的任務。線程池會返回⼀個future類型的對象,可通過future對象判斷線程是否執行成功,獲取返回值等。
線程池關閉的方式也有兩種,shutdown()和shutdownNow()⽅法可關閉線程池。它們的原理是遍歷線程池中的⼯作線程,然後逐個調⽤線程的interrupt⽅法來中斷線程,所以⽆法響應中斷的任務可能永遠⽆法終⽌。
區別在於,shutdownNow 更改狀態成STOP,然後嘗試停⽌所有的正在執⾏或暫停任務的線程,並返回等待執⾏任務的列表,⽽shutdown更改狀態成SHUTDOWN狀態,然後中斷所有沒有正在執⾏任務的線程,正在執行的線程會繼續執行完畢。
附
1.線程池設置多少合適:
有大佬給出了詳細計算模式
https://dayarch.top/p/how-many-threads-should-be-created.html
2. 線程工廠的demo
/**
* 實現線程工廠的接口,然後實現自定義內容。重寫newThread方法
*/
public class MyThreadFactory implements ThreadFactory {
private static int COUNTER = 0;
private static String THREAD_PREFIX = "myThread";
@Override
public Thread newThread(Runnable r) {
int i = COUNTER++;
return new Thread(r, THREAD_PREFIX + i);
}
//使用
public static void main(String[] args) {
MyThreadFactory myThreadFactory = new MyThreadFactory();
Thread thread = myThreadFactory.newThread(new Runnable() {
@Override
public void run() {
}
});
thread.start();
}
}
2. 自定義飽和策略
//需要實現RejectedExecutionHandler,重寫rejectedExecution方法
class MyRejected implements RejectedExecutionHandler {
public MyRejected() {
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("現線程池中的情況爲:" + executor.getActiveCount());
System.out.println("當前被拒絕任務爲:" + r.toString());
}
}