后端学习 - 并发编程
文章目錄
- 一 進(jìn)程與線程
- 1 區(qū)別與聯(lián)系
- 2 Java內(nèi)存區(qū)域
- 3 線程組
- 4 線程的上下文切換
- 5 并發(fā)與并行
- 6 線程的生命周期與狀態(tài)
- 二 線程間的通信和同步
- 1 線程同步:鎖
- 2 線程同步:等待-通知機制
- 3 線程同步:volatile 信號量
- 4 線程通信:管道
- 三 線程死鎖
- 1 死鎖的四個必要條件
- 2 死鎖解決:預(yù)防死鎖、避免死鎖、檢測與解除死鎖
- 四 并發(fā)編程的相關(guān)方法
- 1 sleep() 與 wait()
- 2 run() 與 start()
- 3 join()
- 4 線程創(chuàng)建的四種方法
- 5 獲取與設(shè)置優(yōu)先級(不可靠)、守護(hù)線程
- 五 synchronized 關(guān)鍵字
- 1 簡介
- 2 使用方法
- 3 注意事項
- 六 volatile 關(guān)鍵字
- 1 作用
- 2 雙重校驗鎖實現(xiàn) 單例模式(線程安全)
- 3 與 synchronized 的關(guān)系
- 七 ReentrantLock 可重入鎖
- 1 可重入鎖
- 2 和 synchronized 異同
- 八 ThreadLocal
- 1 概念
- 2 get 和 set 方法的源碼
- 3 ThreadLocalMap - key 的弱引用和 GC
- 九 并發(fā)容器
- 1 ConcurrentHashMap
- 2 CopyOnWriteArrayList
- 3 ConcurrentLinkedQueue
- 4 BlockingQueue
- 5 ConcurrentSkipListMap
- 十 線程池
- 1 為什么使用線程池
- 2 ThreadPoolExecutor 構(gòu)造方法
- 3 ThreadPoolExecutor 的狀態(tài)
- 4 任務(wù)處理流程
一 進(jìn)程與線程
1 區(qū)別與聯(lián)系
- 進(jìn)程是系統(tǒng)分配資源的基本單位,線程是 CPU 調(diào)度的基本單位
- 進(jìn)程和線程本質(zhì)的區(qū)別是是否單獨占有內(nèi)存地址空間及其它系統(tǒng)資源(比如I/O)
- 線程是輕量級進(jìn)程,進(jìn)程在其執(zhí)行的過程中可以產(chǎn)生多個線程。與進(jìn)程不同的是屬于同一進(jìn)程的多個線程共享進(jìn)程的堆和方法區(qū)資源 (JDK1.8 之后的元空間),但每個線程有自己的程序計數(shù)器、虛擬機棧和本地方法棧,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負(fù)擔(dān)要比進(jìn)程小得多
- 使用多線程而非多進(jìn)程實現(xiàn)并發(fā)的優(yōu)勢:進(jìn)程間的通信比較復(fù)雜,而線程間的通信比較簡單;進(jìn)程是重量級的,而線程是輕量級的,故多線程方式的系統(tǒng)開銷更小
2 Java內(nèi)存區(qū)域
- 線程私有程序計數(shù)器的目的是,線程切換后能恢復(fù)到正確的執(zhí)行位置
- 線程私有虛擬機棧和本地方法棧的目的是,保證線程中的 局部變量(存放在棧幀中的局部變量表) 不被別的線程訪問到
- 堆和方法區(qū)是所有 線程共享的資源,其中堆是進(jìn)程中最大的一塊內(nèi)存,主要用于存放新創(chuàng)建的對象 (幾乎所有對象都在這里分配內(nèi)存),方法區(qū)主要用于存放已被加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
3 線程組
- 線程組是一個樹狀的結(jié)構(gòu),每個線程組下面可以有多個線程或者 線程組
- 每個 Thread 必然存在于一個 ThreadGroup 中,Thread 不能獨立于 ThreadGroup 存在
- 如果在 new Thread 時沒有顯式指定,那么默認(rèn)將父線程(當(dāng)前執(zhí)行new Thread的線程)線程組設(shè)置為自己的線程組
- ThreadGroup 是一個標(biāo)準(zhǔn)的向下引用的樹狀結(jié)構(gòu),這樣設(shè)計的原因是防止"上級"線程被"下級"線程引用而無法有效地被GC回收
- 線程組可以起到統(tǒng)一控制線程的優(yōu)先級和檢查線程的權(quán)限的作用
4 線程的上下文切換
- 線程在執(zhí)行過程中會有自己的運行條件和狀態(tài)(也稱上下文),比如上文所說到過的程序計數(shù)器,棧信息等
- 線程切換時,需要保存當(dāng)前線程的上下文,留待線程下次占用 CPU 的時候恢復(fù)現(xiàn)場,并加載下一個將要占用 CPU 的線程上下文
舉例說明:線程A切換到線程B
1.先掛起線程A,將其在CPU中的狀態(tài)保存在內(nèi)存中
2.在內(nèi)存中檢索下一個線程B的上下文,并將其在 CPU 的寄存器中恢復(fù),開始執(zhí)行B線程
3.當(dāng)B執(zhí)行完,根據(jù)程序計數(shù)器中指向的位置恢復(fù)線程A
5 并發(fā)與并行
- 并發(fā): 同一時間段,多個任務(wù)都在執(zhí)行 (單位時間內(nèi)不一定同時執(zhí)行,可以來回切換);
- 并行: 單位時間內(nèi),多個任務(wù)同時執(zhí)行
6 線程的生命周期與狀態(tài)
反復(fù)調(diào)用同一個線程的start()方法是否可行?假如一個線程執(zhí)行完畢(此時處于TERMINATED狀態(tài)),再次調(diào)用這個線程的 start() 方法是否可行?
兩個問題的答案都是不可行,因為 threadStatus 的值會改變,調(diào)用 start() 的前提是 threadStatus==0 ,此時再次調(diào)用 start() 方法會拋 IllegalThreadStateException異常
二 線程間的通信和同步
1 線程同步:鎖
- 線程同步的根本目的是讓線程按照一定的順序執(zhí)行
- 示例
2 線程同步:等待-通知機制
- 基于 Object 類的 wait() 方法和 notify()(隨機叫醒一個正在等待的線程) ,notifyAll()(叫醒所有正在等待的線程) 方法實現(xiàn)
- 一個鎖同一時刻只能被一個線程持有。假如線程A現(xiàn)在持有了一個鎖 lock 并開始執(zhí)行,它可以使用 lock.wait() 讓自己進(jìn)入等待狀態(tài),lock 被釋放
- 線程B獲得了 lock 這個鎖并開始執(zhí)行,它可以在某一時刻,使用 lock.notify(),通知之前持有 lock 鎖并進(jìn)入等待狀態(tài)的線程A,指示線程A繼續(xù)執(zhí)行(需要注意的是,這個時候線程B并沒有釋放鎖 lock,除非線程B這個時候使用 lock.wait() 釋放鎖,或者線程B執(zhí)行結(jié)束自行釋放鎖,線程A才能得到 lock 鎖)
3 線程同步:volatile 信號量
- volatile 關(guān)鍵字能夠保證內(nèi)存的可見性,如果用 volatile 關(guān)鍵字聲明了一個變量,在一個線程里面改變了這個變量的值,那其它線程是立馬可見更改后的值的;同時,它可以禁止指令重排
- 多個線程(超過2個)需要相互合作,用簡單的“鎖”和“等待通知機制”就不那么方便了。這個時候就可以用到信號量
4 線程通信:管道
- JDK提供了 PipedWriter、 PipedReader、 PipedOutputStream、 PipedInputStream。其中,前面兩個是基于字符的(處理單元為2字節(jié)),后面兩個是基于字節(jié)流的(處理單元為1字節(jié))
三 線程死鎖
1 死鎖的四個必要條件
- 互斥條件:該資源任意一個時刻只由一個線程占用
- 請求與保持條件:一個進(jìn)程因請求資源而阻塞時,對已獲得的資源保持不放
- 不剝奪條件:線程已獲得的資源在未使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源
- 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系
2 死鎖解決:預(yù)防死鎖、避免死鎖、檢測與解除死鎖
- 破壞請求與保持條件 :一次性申請所有的資源
- 破壞不剝奪條件 :占用部分資源的線程進(jìn)一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源
- 破壞循環(huán)等待條件 :靠按序申請資源來預(yù)防。按某一順序申請資源,釋放資源則反序釋放。破壞循環(huán)等待條件
操作系統(tǒng):死鎖
四 并發(fā)編程的相關(guān)方法
1 sleep() 與 wait()
- sleep() 方法沒有釋放鎖,而 wait() 方法釋放了鎖
- 都可以暫停線程的執(zhí)行
- wait() 通常被用于線程間交互/通信,sleep() 通常被用于暫停執(zhí)行
- wait() 方法被調(diào)用后,線程不會自動蘇醒,需要別的線程調(diào)用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執(zhí)行完成后,線程會自動蘇醒?;蛘呖梢允褂?wait(long timeout) 超時后線程會自動蘇醒
- wait() 可以指定時間,也可以不指定;而 sleep() 必須指定時間
- wait() 釋放CPU資源,同時釋放鎖;sleep() 釋放CPU資源,但是不釋放鎖,所以易死鎖
為什么 sleep 函數(shù)的精度很低?
- sleep函數(shù)并不能起到定時的作用,主要作用是延時。在一些多線程中可能會看到sleep(0),其主要目的是讓出時間片
- 當(dāng)系統(tǒng)越繁忙的時候它精度也就越低,因為它的精度取決于線程自身優(yōu)先級、其他線程的優(yōu)先級,以及線程的數(shù)量等因素,所以說sleep 函數(shù)是不能用來精確計時的
2 run() 與 start()
- 調(diào)用 start() 方法,會啟動一個線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時間片后就可以開始運行了。 start() 會執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動執(zhí)行 run() 方法的內(nèi)容,實現(xiàn)多線程工作
- 直接執(zhí)行 run() 方法,會把 run() 方法當(dāng)成一個 main 線程下的普通方法去執(zhí)行,并不會在某個線程中執(zhí)行它,所以不是多線程工作
3 join()
- 在 a 線程中調(diào)用 b 線程的 join() 方法,線程 a 進(jìn)入阻塞狀態(tài),直到線程 b 完全執(zhí)行完,線程 a 從阻塞狀態(tài)中恢復(fù)
- 如果主線程想等待子線程執(zhí)行完畢后,獲得子線程中的處理完的某個數(shù)據(jù),就使用 join()
4 線程創(chuàng)建的四種方法
- Runnable 接口不會返回結(jié)果或拋出檢查異常,Callable 接口可以,如果任務(wù)不需要返回結(jié)果或拋出異常推薦使用 Runnable,這樣代碼看起來會更加簡潔
5 獲取與設(shè)置優(yōu)先級(不可靠)、守護(hù)線程
- Java 只是給操作系統(tǒng)一個優(yōu)先級的 參考值,線程最終在操作系統(tǒng)的調(diào)用順序由操作系統(tǒng)的線程調(diào)度算法決定的
- 優(yōu)先級獲取:thread_instance.getPriority()
- 優(yōu)先級設(shè)置:setPriority(int LEVEL),默認(rèn)5,最高10,最低1,優(yōu)先級越高,先執(zhí)行的 概率 更大
- 線程組也具有優(yōu)先級,如果某個線程優(yōu)先級大于線程所在線程組的最大優(yōu)先級,那么該線程的優(yōu)先級被線程組的最大優(yōu)先級取代
- 守護(hù)線程默認(rèn)的優(yōu)先級比較低
- 如果某線程是守護(hù)線程,那如果所有的非守護(hù)線程都結(jié)束了,這個守護(hù)線程也會自動結(jié)束,就免去了還要繼續(xù)關(guān)閉子線程的麻煩
五 synchronized 關(guān)鍵字
1 簡介
- Java 多線程的鎖都是基于對象的,Java中的每一個對象都可以作為一個鎖
- 一句話概括:synchronized 關(guān)鍵字解決的是多個線程之間訪問資源的同步性,synchronized 關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執(zhí)行
- 在 Java 早期版本中,synchronized 屬于 重量級鎖,效率低下。因為操作系統(tǒng)實現(xiàn)線程之間的切換時需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)(通過 trap 指令),這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間
2 使用方法
要求是多個線程共用一把鎖
synchronized(this) {// TODO }3 注意事項
- 不要使用 synchronized(String a),因為 JVM 中,字符串常量池具有緩存功能
- 構(gòu)造方法不能使用 synchronized 關(guān)鍵字修飾。因為構(gòu)造方法本身就屬于線程安全的,不存在同步的構(gòu)造方法一說
六 volatile 關(guān)鍵字
1 作用
因為在 Java 內(nèi)存模型中,在 JDK1.2 之前,Java 的內(nèi)存模型實現(xiàn)總是從主存(即共享內(nèi)存)讀取變量,是不需要進(jìn)行特別的注意的。而 在當(dāng)前的 Java 內(nèi)存模型下,線程可以把變量保存本地內(nèi)存(比如機器的寄存器,CPU cache)中,而不是直接在主存中進(jìn)行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續(xù)使用它在寄存器中的變量值的拷貝,造成數(shù)據(jù)的不一致
使用 volatile 關(guān)鍵字,指定變量每次使用時都從主存讀取
2 雙重校驗鎖實現(xiàn) 單例模式(線程安全)
public class Singleton {private volatile static Singleton uniqueInstance; // 對象實例,需要用volatile修飾private Singleton() { // 構(gòu)造方法,設(shè)置為private}public static Singleton getUniqueInstance() { // 創(chuàng)建實例//先判斷對象是否已經(jīng)實例過,沒有實例化過才進(jìn)入加鎖代碼if (uniqueInstance == null) {//類對象加鎖,保證只能創(chuàng)建一個實例synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();// 執(zhí)行過程:// 1.為 uniqueInstance 分配內(nèi)存空間// 2.初始化 uniqueInstance// 3.將 uniqueInstance 指向分配的內(nèi)存地址}}}return uniqueInstance;} }必須使用 volatile 關(guān)鍵字的原因:
由于 JVM 具有指令重排的特性,uniqueInstance = new Singleton() 執(zhí)行順序有可能變成注釋中的 1->3->2。指令重排在單線程環(huán)境下不會出現(xiàn)問題,但是在多線程環(huán)境下會導(dǎo)致一個線程獲得還沒有初始化的實例(僅僅是剛分配了內(nèi)存空間)
線程 T1 執(zhí)行了 1 和 3,此時 T2 調(diào)用 getUniqueInstance() 后發(fā)現(xiàn) uniqueInstance 不為空,因此返回 uniqueInstance,但此時 uniqueInstance 還未被初始化。 使用 volatile 禁止 JVM 的指令重排,保證在多線程環(huán)境下也能正常運行
3 與 synchronized 的關(guān)系
- synchronized 關(guān)鍵字和 volatile 關(guān)鍵字是互補而非對立的關(guān)系
- volatile 關(guān)鍵字是線程同步的 輕量級 實現(xiàn),性能比 synchronized 關(guān)鍵字好
- volatile 關(guān)鍵字只能用于變量(僅僅保證對單個volatile變量的讀/寫具有原子性),而 synchronized 關(guān)鍵字可以修飾方法以及代碼塊
- volatile 關(guān)鍵字主要用于解決變量在多個線程之間的 可見性 ,而 synchronized 關(guān)鍵字解決的是多個線程之間訪問資源的同步性
七 ReentrantLock 可重入鎖
該部分的參考
1 可重入鎖
- 可重入鎖,指的是一個線程能夠?qū)σ粋€臨界資源重復(fù)加鎖
- AQS 有一個變量 state 用于記錄同步狀態(tài):初始情況下,state = 0,表示 ReentrantLock 目前處于解鎖狀態(tài)。如果有線程調(diào)用 lock 方法進(jìn)行加鎖,state 就由0變?yōu)?,如果該線程再次調(diào)用 lock 方法加鎖,就執(zhí)行 state++。線程每調(diào)用一次 unlock 方法釋放鎖,會讓 state–。通過查詢 state 的數(shù)值,即可知道 ReentrantLock 被重入的次數(shù)了
- 現(xiàn)在有方法 m1 和 m2,兩個方法均使用了同一把鎖對方法進(jìn)行同步控制,同時方法 m1 會調(diào)用 m2。線程 t 進(jìn)入方法 m1 成功獲得了鎖,此時線程 t 要在沒有釋放鎖的情況下,調(diào)用 m2 方法。由于 m1 和 m2 使用的是同一把可重入鎖,所以線程 t 可以進(jìn)入方法 m2,并再次獲得鎖,而不會被阻塞住;假如 lock 是不可重入鎖,那么上面的示例代碼必然會引起死鎖情況的發(fā)生
2 和 synchronized 異同
- synchronized 使用的是對象或類進(jìn)行加鎖,而 ReentrantLock 內(nèi)部是通過 AQS(AbstractQueuedSynchronizer)中的同步隊列進(jìn)行加鎖
- 公平與非公平指的是線程獲取鎖的方式:
公平模式下,線程在同步隊列中通過 FIFO 的方式獲取鎖,每個線程最終都能獲取鎖,缺點是效率低
在非公平模式下,線程會通過“插隊”的方式去搶占鎖,搶不到的則進(jìn)入同步隊列進(jìn)行排隊,缺點是可能出現(xiàn)線程饑餓
八 ThreadLocal
參考鏈接
1 概念
- ThreadLocal 類主要解決的就是讓每個線程綁定自己的值,這個值不能被其它線程訪問到
- 每個 Thread 中都具備一個容器 ThreadLocalMap,而 ThreadLocalMap 可以存儲以 ThreadLocal 為 key (其實是 ThreadLocal 的弱引用),Object 對象為 value 的鍵值對
- ThrealLocal 類中可以通過 Thread.currentThread() 獲取到當(dāng)前線程對象后,直接通過 getMap(Thread t) 可以訪問到該線程的 ThreadLocalMap 對象
- ThreadLocalMap 不使用拉鏈法解決哈希沖突,而是向后探測:如果先遇到了空位置則直接插入;如果先遇到了 key 過期的數(shù)據(jù)則進(jìn)行垃圾回收并替換
2 get 和 set 方法的源碼
public void set(T value) {Thread t = Thread.currentThread(); // 獲取當(dāng)前的線程ThreadLocalMap map = getMap(t); // 每一個線程都維護(hù)各自的一個容器(ThreadLocalMap)if (map != null)map.set(this, value); // 這里的key對應(yīng)的是ThreadLocalelsecreateMap(t, value); // 默認(rèn)map是null,第一次往其中添加數(shù)據(jù)時,執(zhí)行初始化}public T get() {Thread t = Thread.currentThread(); // 獲取當(dāng)前的線程ThreadLocalMap map = getMap(t); // 當(dāng)前線程的ThreadLocalMapif (map != null) {ThreadLocalMap.Entry e = map.getEntry(this); // this指的是ThreadLocal對象if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value; // entry.value就可以獲取到工具箱了return result;}}return setInitialValue();}3 ThreadLocalMap - key 的弱引用和 GC
- ThreadLocalMap 中,key 是它對應(yīng)的 ThreadLocal 的弱引用
- 強引用不存在的話,那么 key 就會被回收,也就是會出現(xiàn) value 沒被回收,key 被回收的情況,導(dǎo)致 value 永遠(yuǎn)存在,出現(xiàn)內(nèi)存泄漏
九 并發(fā)容器
1 ConcurrentHashMap
- 數(shù)據(jù)結(jié)構(gòu):
| ConcurrentHashMap JDK1.7 | Segment 數(shù)組 + HashEntry 數(shù)組 + 鏈表/紅黑樹 | Segment(本質(zhì)是 ReentrantLock),每次鎖若干 HashEntry |
| ConcurrentHashMap JDK1.8 | Node 數(shù)組 + 鏈表/紅黑樹 | synchronized,每次鎖一個 Node |
| Hashtable | 數(shù)組+鏈表 | synchronized,每次鎖全表 |
JDK1.8 的時候已經(jīng)摒棄了 Segment 的概念,synchronized 只鎖定當(dāng)前鏈表或紅黑二叉樹的首節(jié)點,并發(fā)控制使用 synchronized 和 CAS 來操作,雖然在 JDK1.8 中還能看到 Segment 的數(shù)據(jù)結(jié)構(gòu),但是已經(jīng)簡化了屬性,只是為了兼容舊版本
Hashtable(同一把鎖) :使用 synchronized 來保證線程安全,效率非常低下。當(dāng)一個線程訪問同步方法時,其他線程也訪問同步方法,可能會進(jìn)入阻塞或輪詢狀態(tài),如使用 put 添加元素,另一個線程不能使用 put 添加元素,也不能使用 get,競爭會越來越激烈效率越低
2 CopyOnWriteArrayList
- 線程安全的 List,在讀多寫少的場合性能非常好,遠(yuǎn)遠(yuǎn)好于 Vector
- CopyOnWriteArrayList 讀取完全不用加鎖,寫入也不會阻塞讀取操作
- 寫時復(fù)制的思想:想要對一塊內(nèi)存進(jìn)行修改時,不在原有內(nèi)存塊中進(jìn)行寫操作,而是將內(nèi)存拷貝一份,在新的內(nèi)存中進(jìn)行寫操作,寫完之后將指向原來內(nèi)存指針指向新的內(nèi)存,原來的內(nèi)存就可以被回收掉了
3 ConcurrentLinkedQueue
- 高效的并發(fā)隊列,非阻塞隊列(通過 CAS 操作實現(xiàn))
- 使用鏈表實現(xiàn),可以看做一個線程安全的 LinkedList
4 BlockingQueue
- 接口,阻塞隊列(通過加鎖實現(xiàn)),適合用于作為數(shù)據(jù)共享的通道
- 被廣泛使用在 “生產(chǎn)者-消費者” 問題中,其原因是 BlockingQueue 提供了可阻塞的插入和移除的方法。當(dāng)隊列容器已滿,生產(chǎn)者線程會被阻塞,直到隊列未滿;當(dāng)隊列容器為空時,消費者線程會被阻塞,直至隊列非空時為止
5 ConcurrentSkipListMap
- 使用跳表實現(xiàn)的 Map,使用跳表的數(shù)據(jù)結(jié)構(gòu)進(jìn)行快速查找
- 對平衡樹的插入和刪除往往很可能導(dǎo)致平衡樹進(jìn)行一次全局的調(diào)整,而對跳表的插入和刪除只需要對整個數(shù)據(jù)結(jié)構(gòu)的局部進(jìn)行操作即可。這樣帶來的好處是:在高并發(fā)的情況下,需要一個全局鎖來保證整個平衡樹的線程安全。而對于跳表,只需要部分鎖
- 跳表內(nèi)所有的元素都是排序的,對跳表進(jìn)行遍歷會得到有序的結(jié)果,適用于數(shù)據(jù)需要有序的環(huán)境
十 線程池
參考鏈接
1 為什么使用線程池
- 創(chuàng)建/銷毀線程需要消耗系統(tǒng)資源,線程池可以復(fù)用已創(chuàng)建的線程
- (主要原因)控制并發(fā)的數(shù)量:并發(fā)數(shù)量過多,可能會導(dǎo)致資源消耗過多,從而造成服務(wù)器崩潰
- 統(tǒng)一管理線程
2 ThreadPoolExecutor 構(gòu)造方法
- Java中的線程池頂層接口是 Executor 接口,ThreadPoolExecutor 是這個接口的實現(xiàn)類
| int corePoolSize | 線程池中核心線程數(shù)最大值 | 核心線程默認(rèn)情況下會一直存在于線程池中,即使這個核心線程什么都不干(鐵飯碗),而非核心線程如果長時間的閑置,就會被銷毀(臨時工) | 是 |
| int maximumPoolSize | 線程池中線程總數(shù)最大值 | 核心線程數(shù)量 + 非核心線程數(shù)量 | 是 |
| long keepAliveTime | 非核心線程閑置超時時長 | 非核心線程如果處于閑置狀態(tài)超過該值,就會被銷毀。如果設(shè)置 allowCoreThreadTimeOut(true),則會也作用于核心線程 | 是 |
| TimeUnit unit | keepAliveTime 的單位 | 枚舉類型 | 是 |
| BlockingQueue workQueue | 阻塞隊列,維護(hù)著等待執(zhí)行的 Runnable 任務(wù)對象 | 下面補充說明 | 是 |
| ThreadFactory threadFactory | 線程創(chuàng)建工廠 | 用于批量創(chuàng)建線程,統(tǒng)一在創(chuàng)建線程時設(shè)置一些參數(shù),如是否守護(hù)線程、線程的優(yōu)先級等。如果不指定,會新建一個默認(rèn)的線程工廠 | 否 |
| RejectedExecutionHandler handler | 拒絕處理策略,線程數(shù)量大于最大線程數(shù)就會采用拒絕處理策略 | 下面補充說明 | 否 |
- 常用的阻塞隊列
- 有關(guān)拒絕處理策略
3 ThreadPoolExecutor 的狀態(tài)
| RUNNING | 線程池創(chuàng)建后處于 RUNNING 狀態(tài) |
| SHUTDOWN | 調(diào)用 shutdown() 方法后處于 SHUTDOWN 狀態(tài),線程池不能接受新的任務(wù),正在執(zhí)行的線程不中斷,完成阻塞隊列的任務(wù)(存疑) |
| STOP | 調(diào)用 shutdownNow() 方法后處于 STOP 狀態(tài),線程池不能接受新的任務(wù),中斷所有線程,阻塞隊列中沒有被執(zhí)行的任務(wù)全部丟棄。此時,工作線程全部停止,阻塞隊列為空 |
| TIDYING | 當(dāng)所有的任務(wù)已終止,線程池會變?yōu)?TIDYING 狀態(tài),接著會執(zhí)行 terminated() 函數(shù) |
| TERMINATED | 線程池處在 TIDYING 狀態(tài)時,并且執(zhí)行完 terminated() 方法之后 , 線程池被設(shè)置為 TERMINATED 狀態(tài) |
4 任務(wù)處理流程
為什么在步驟2中,要二次檢查線程池的狀態(tài)?
- 在多線程的環(huán)境下,線程池的狀態(tài)是時刻發(fā)生變化的。有可能剛獲取線程池狀態(tài)后線程池狀態(tài)就改變了。判斷是否將 command 加入workqueue 是線程池之前的狀態(tài)。倘若沒有二次檢查,萬一線程池處于非 RUNNING 狀態(tài)(在多線程環(huán)境下很有可能發(fā)生),那么 command 永遠(yuǎn)不會執(zhí)行
- 類似于單例模式的雙重校驗
總結(jié)
以上是生活随笔為你收集整理的后端学习 - 并发编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 后端学习 - Java容器
- 下一篇: 阿里云地图的全方位搜索