下面將依次介紹:java
1. 線程狀態緩存
2. start方法源碼app
3. 什麼是線程池?ide
4. 線程池的工做原理和使用線程池的好處工具
5. ThreadPoolExecutor中的Workerpost
6. 線程池工具類如Executors等this
7. 如何中止一個線程(含代碼)spa
8. 如何合理的配置 Java 線程池?如 CPU 密集型的任務,基本線程池 應該配置多大?IO 密集型的任務,基本線程池應該配置多大?用有界隊列好仍是無界隊列好?任務很是多的時候,使用什麼阻塞隊列能獲取 最好的吞吐量?.net
線程的生命週期:線程
首先new一個線程對象,進入New狀態;
調用線程的start方法線程進入Runnable就緒狀態,等待CPU調度;
而後該線程任務被CPU執行時,處於Running狀態,若是放棄了CPU資源(如調用了yield方法),會進入到Runnable狀態,等待CPU調度;
執行過程當中有block IO(如DataInputStream.readUTF()方法)、未獲取到鎖、sleep、wait、join(join底層調用的Object的wait方法)、condition.await等操做,進入Blocked狀態;
與之對應的若是線程處於Blocked狀態,block IO 完成、獲取到鎖(也不會當即被CPU執行),sleep時間完、被notify或notifyALL或condition.singal或condition.singalAll喚醒,由Blocked轉爲Runnable狀態,等待CPU執行;
Terminated結束狀態,線程run方法執行完畢、或者異常退出(好比其餘線程調用了該線程的 interrupt方法(該方法僅僅只是設置線程標誌位),若是該線程正在sleep/wait/join會拋出
InterruptedException異常,線程終止,見本文末尾如何終止一個線程)。
注:Condition能夠由ReentrantLock對象的newCondition生成(底層就是內部類Sync繼承了AQS,sync對象生成的Condition對象),Condition對象與一個ReentrantLock對象相對應,能夠喚醒該ReentrantLock對象對應的Blocked線程。
ThreadGroup
中;start0
是最核心的方法了,就是運行狀態爲 NEW (內部狀態標識爲 0) 的線程;start0
是個 native
方法,也就是 JNI 方法;看到這裏,你也許會有個疑問,本身重寫的 run 方法是何時被調用的呢?源碼中也沒看到調用啊!!
Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
上面這段截自 JDK 官方文檔,意思是說:
run 方法是在調用 JNI 方法 start0() 的時候被調用的,被調用後,咱們寫的邏輯代碼才得以被執行。
線程池源碼裏最主要的類:ThreadPoolExecutor;
對該類的源碼進行部分解析;
線程池重要的兩個狀態:
runState:線程池運行狀態
workerCount:工做線程的數量
線程池用一個32位的int來同時保存runState和workerCount,其中高3位(第31到29位)是runState,其他29位是workerCount(大約500 million);以下圖
一個線程池管理了一組工做線程,同時它還包括了一個用於放置等待執行任務的任務隊列(阻塞隊列)。
默認狀況下,在建立了線程池後,線程池中的線程數爲 0。
當任務提交給 線程池以後的處理策略以下:
1. 若是此時線程池中的數量小於 corePoolSize(核心池的大小),即便線程池中的線程都處於空閒狀態,也要建立新的線程來處理被添加的任務(也 就是每來一個任務,就要建立一個線程來執行任務)。 2. 若是此時線程池中的數量大於等於 corePoolSize,可是緩衝隊列 workQueue 未滿,那麼任務被放入緩衝隊列,則該任務會等待空閒線程將其 取出去執行。
3. 如 果 此 時 線 程 池 中 的 數 量 大 於 等 於 corePoolSize , 緩 衝 隊 列 workQueue 滿,而且線程池中的數量小於 maximumPoolSize(線程池 最大線程數),建新的線程來處理被添加的任務。
4. 若是 此時 線程 池中 的數量 大 於 等 於 corePoolSize, 緩 衝 隊列 workQueue 滿,而且線程池中的數量等於 maximumPoolSize,那麼通 過 RejectedExecutionHandler 所指定的策略(任務拒絕策略)來處理此任務;也就是處理任務的優先級爲:核心線程 corePoolSize、任務隊列 workQueue、最大線程 maximumPoolSize,若是三者都滿了,使用 handler 處理被拒絕的任務。
5. 特別注意,在 corePoolSize 和 maximumPoolSize 之間的線程 數會被自動釋放。當線程池中線程數量大於 corePoolSize 時,若是某線程 空閒時間超過 keepAliveTime,線程將被終止,直至線程池中的線程數目不大於 corePoolSize。這樣,線程池能夠動態的調整池中的線程數。
6. threadFactory
建立線程的工廠。全部的線程都是經過這個Factory建立的。
默認會使用
Executors.defaultThreadFactory 來做線程工廠。
7. handler 線程池的飽和策略。作不了任務了找理由罷工
AbortPolicy
直接拋出異常,默認策略;
CallerRunsPolicy
用調用者所在的線程來執行任務;
DiscardOldestPolicy
丟棄阻塞隊列中靠最前的任務,並執行當前任務;
DiscardPolicy
直接丟棄任務。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Worker既實現了Runnable,又繼承了AbstractQueuedSynchronizer(AQS),因此它既是一個可執行的任務,又能夠達到鎖的效果。
1.經過重複利用已建立的線程,減小在建立和銷燬線程上所花的時間以及系統資源的開銷。
2.提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能夠當即執行。
3.提升線程的可管理性。使用線程池能夠對線程進行統一的分配和監控。
4.若是不使用線程池,有可能形成系統建立大量線程而致使消耗完系統內存。
對於原理,有幾個接口和類值得咱們關注:
Executor 接口
Executors 類
ExecutorService 接口
AbstractExecutorService 抽象類
ThreadPoolExecutor 類
Executor 是一個頂層接口,在它裏面只聲明瞭一個方法 execute(Runnable),返回 值爲 void,參數爲 Runnable 類型,從字面意思能夠理解,就是用來執行傳進去的任務的;
而後 ExecutorService 接口繼承了 Executor 接口,並聲明瞭一些方法:submit、 invokeAll、invokeAny 以及 shutDown 等;
抽象類 AbstractExecutorService 實現了 ExecutorService 接口,基本實現了 ExecutorService 中聲明的全部方法;
而後 ThreadPoolExecutor 繼承了類 AbstractExecutorService。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 建立一個支持定時及週期性的任務執行的線程池,多數狀況下可用來替代 Timer 類。
1. newSingleThreadExecutor
建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。 此線程池保證全部任務的執行順序按照任務的提交順序執行。
2.newFixedThreadPool
建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的 最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結 束,那麼線程池會補充一個新線程。
3. newCachedThreadPool
建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60 秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添 加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系 統(或者說 JVM)可以建立的最大線程大小。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
在 ThreadPoolExecutor 類中有幾個很是重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute 和 submit 區別: submit 有返回值,execute 沒有返回值。 因此說能夠根據任務有無返回 值選擇對應的方法。 submit 方便異常的處理。 若是任務可能會拋出異常,並且但願外面的調 用者可以感知這些異常,那麼就須要調用 submit 方法,經過捕獲 Future.get 拋出的異常。
shutdown()和 shutdownNow()的區別: shutdown()和 shutdownNow()是用來關閉線程池的。
shutdown 方法:此方法執行後不得向線程池再提交任務,若是有空閒線程則銷燬空閒線程,等待全部正在執行的任務及位於阻塞隊列中的任務執行結束,而後銷燬全部線程。
shutdownNow 方法:此方法執行後不得向線程池再提交任務,若是有空閒線程則銷燬空閒線程,取消全部位於阻塞隊列中的任務,並將其放入 List容器,做爲返回值;取消正在執行的線程(實際上僅僅是設置正在執行線程的中斷標誌位,調用線程的 interrupt 方法來中斷線程)。
https://blog.csdn.net/zbw18297786698/article/details/53432879
1. 設置標誌位
能夠用一個boolean類型的變量
2.利用Java線程的中斷
和上面的標誌位有些許相似;
線程A被其餘線程調用了interrupt方法設置線程A的標誌位,線程A run方法內部判斷Thread.currentThread().isInterrupted(),若是true就中止,前提是線程A不處於sleep/wait/yield/join;
中斷能夠理解爲線程的一個標誌位屬性,它表示一個運行中的線程是否被其餘線程進行了中斷操做。
中斷比如其餘線程對該線程打了個招呼,其餘線程經過調用該線程的interrupt()方法對其進行中斷操做。
線程經過檢查自身是否被中斷來進行響應,線程經過方法isInterrupted()來進行判斷是否被中斷,也能夠調用靜態方法Thread.interrupted()對當前線程的中斷標識位進行復位。
若是該線程已經處於終結狀態,即便該線程被中斷過,在調用該線程對象的isInterrupted()時依舊會返回false。
import java.util.concurrent.TimeUnit; public class InterruptThreadStateTest { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Worker(), "WorkerThread"); t.start(); TimeUnit.MILLISECONDS.sleep(100); System.out.println("ThreadName: "+ Thread.currentThread().getName() +" call ThreadName:" + t.getName()+" Method:interrupt"); t.interrupt(); TimeUnit.MILLISECONDS.sleep(500); System.out.println("ThreadName:"+ t.getName()+" state : " + t.getState()); } private static class Worker implements Runnable{ @Override public void run() { Thread t = Thread.currentThread(); while (true) { System.out.println("ThreadName:" + t.getName() + " state : " + t.getState()); if (t.isInterrupted()) { System.out.println("ThreadName:" + t.getName() + " 被中斷"); return; }else{ System.out.println("ThreadName:" + t.getName() + " 沒有被中斷"); } } } } }
run方法執行完畢,線程狀態Terminated。
3. 利用拋出InterruptedException的方式
中斷能夠理解爲線程的一個標誌位屬性,它表示一個運行中的線程是否被其餘線程進行了中斷操做。
中斷比如其餘線程對該線程打了個招呼,其餘線程經過調用該線程的interrupt()方法對其進行中斷操做。
線程經過檢查自身是否被中斷來進行響應,線程經過方法isInterrupted()來進行判斷是否被中斷,也能夠調用靜態方法Thread.interrupted()對當前線程的中斷標識位進行復位。若是該線程已經處於終結狀態,即便該線程被中斷過,在調用該線程對象的isInterrupted()時依舊會返回false。
從Java的API中能夠看到,許多聲明拋出InterruptedException的方法(例如Thread.sleep(longmillis)方法,當線程在sleep/wait/yield/join時,若是被中斷,這個異常就會產生)。
這些方法在拋InterruptedException以前,Java虛擬機會先將該線程的中斷標識位清除,而後拋出InterruptedException,此時調用isInterrupted()方法將會返回false。
public class ThreadStopSafeInterrupted { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { // 使用中斷機制,來終止線程
if (Thread.currentThread().isInterrupted()) { System.out.println("Interrupted ..."); break; } try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("Interrupted When Sleep ..."); // Thread.sleep()方法因爲中斷拋出異常。 // Java虛擬機會先將該線程的中斷標識位清除,而後拋出InterruptedException, // 由於在發生InterruptedException異常的時候,會清除中斷標記 // 若是不加處理,那麼下一次循環開始的時候,就沒法捕獲這個異常。 // 故在異常處理中,再次設置中斷標記位
Thread.currentThread().interrupt(); } } } }; // 開啓線程
thread.start(); Thread.sleep(2000); thread.interrupt(); } }
答:
1)配置線程池時,CPU 密集型任務能夠少配置線程數,大概和機器的 cpu 核數相 當,可使得每一個線程都在執行任務。
2)IO 密集型任務則因爲須要等待 IO 操做,線程並非一直在執行任務,則配置盡 可能多的線程,2*cpu 核數。
3)有界隊列和無界隊列的配置需區分業務場景,通常狀況下配置有界隊列,在一些 可能會有爆發性增加的狀況下使用無界隊列。
4)任務很是多時,使用非阻塞隊列,使用 CAS 操做替代鎖能夠得到好的吞吐量。 synchronousQueue 吞吐率最高。
參考來源: