[轉] 《Java併發程式設計的藝術》筆記
阿新 • • 發佈:2018-12-09
第一章 併發程式設計的挑戰
- 略
第二章 Java併發機制的底層實現原理
- volatile的兩條實現原則:
- Lock字首指令會引起處理器快取回寫到記憶體
- 一個處理器的快取回寫到記憶體會導致其他處理器的快取無效。
- volatile的使用優化:共享變數會被頻繁讀寫時,可以通過追加為64位元組以提高併發程式設計的效率。因為目前主流處理器快取記憶體行是64個位元組寬,不支援部分填充快取行,通過追加到64位元組的方式填滿高速緩衝區的快取行,避免各元素載入到同一快取行而互相鎖定。(Java7後可能不生效,因為Java7更智慧,會淘汰或重新排列無用欄位,需要使用其他追加位元組的方式)
- Java物件頭
長度 | 內容 | 說明 |
---|---|---|
32/64bit | Mark Word | 儲存物件的hashCode或鎖資訊等 |
32/64bit | Class Metadata Address | 儲存到物件型別資料的指標 |
32/64bit | Array Length | 陣列的長度(僅噹噹前物件為陣列時存在) |
- CAS操作,即Compare And Swap,比較並交換。CAS操作需要輸入兩個數值,一箇舊值(期望操作前的值)和一個新值,在操作期間先比較舊值有沒有發生變化,如果沒有則交換成新值,否則不交換。
- 鎖有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。這幾種狀態會隨著競爭情況逐漸升級,只升不降。
- 偏向鎖
- 當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中的所記錄裡儲存鎖偏向的執行緒ID,以後該執行緒再次加鎖解鎖時不需要進行CAS操作,只需簡單地測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。
- 偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他執行緒常識競爭偏向鎖時,豉油偏向鎖的執行緒才會釋放鎖。
- 偏向鎖的撤銷,需要等待全域性安全點(在這個時間點上沒有正在執行的位元組碼)。它會首先暫停擁有偏向鎖的執行緒,然後檢查豉油偏向鎖的執行緒是否活著,如果執行緒不處於活動狀態,則將物件頭設定成無鎖狀態;如果執行緒仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向物件的鎖記錄,棧中的所記錄和物件頭的Mark Word要麼重新偏向於其他執行緒,要麼恢復到無鎖或者標記物件不適合作為偏向鎖,最後喚醒暫停的執行緒。
- 偏向鎖預設啟動,但是它在應用程式啟動幾秒鐘之後才啟用,如有必要可以使用JVM引數來關閉延遲:-XX:BiasedLockingStartupDelay=0。如果確定應用程式裡所有的鎖通常情況下處於競爭狀態,可以通過JVM引數來關閉偏向鎖:-XX:-UseBiasedLocking=false,那麼程式預設會進入輕量級鎖狀態。
- 輕量級鎖
- 執行緒在執行同步塊之前,JVM會現在當前執行緒的棧幀中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中(Displaced Mark Word)。然後執行緒嘗試使用CAS將物件頭中的Mark Word奇幻為指向鎖記錄的指標。如果成功則當前執行緒獲得鎖,否則說明其他執行緒競爭鎖,當前執行緒嘗試使用自旋來獲取鎖。
- 輕量級鎖解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到物件頭,如果成功則表示沒有競爭發生,否則表示當前鎖存在競爭,鎖會升級為重量級鎖。
- 重量級鎖
- 其他執行緒檢視獲取鎖時都被阻塞,持有鎖的執行緒釋放鎖之後喚醒這些執行緒,被喚醒的執行緒再搶。
- 鎖的優缺點對比
鎖 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖無需額外的消耗,和執行非同步方法相比僅存在納秒級的差距 | 如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷的消耗 | 適用於只有一個執行緒訪問同步塊場景 |
輕量級鎖 | 競爭的執行緒不會阻塞,提高了程式的響應速度 | 如果始終得不到鎖競爭的執行緒,使用自旋會消耗CPU | 追求響應時間,同步塊執行速度非常快 |
重量級鎖 | 執行緒競爭不適用自旋,不會消耗CPU | 執行緒阻塞,響應時間緩慢 | 追求吞吐量,同步塊執行速度較慢 |
- 使用迴圈CAS實現原子操作,JDK的併發包提供了一些類支援原子操作,如AtomicInteger等。
- CAS實現原子操作的三大問題
- ABA問題。一個值從A變成B又變成A,使用CAS檢查時以為沒有變化,但實際上卻變化了。解決思路是使用版本號。
- 迴圈時長長,開銷大。
- 只能保證一個共享變數的原子操作。(Java1.5以後,JDK提供了AtomicReference類來保證引用物件之間的原子性,可以把多個變數放在一個物件裡來進行CAS操作)
第三章 Java記憶體模型
- 併發程式設計模型的兩個關鍵問題:執行緒之間如何通訊及執行緒之間如何同步
- 在指令式程式設計中,執行緒之間的通訊機制有兩種:共享記憶體和訊息傳遞。
- 記憶體屏障型別表
屏障型別 | 說明 |
---|---|
LoadLoad Barriers | 確保Load1資料的裝載先於Load2及所有後序裝置指令的裝載 |
StoreStore Barriers | 確保Store1資料對其他處理器可見(重新整理到記憶體)先於Store2及所有後序儲存指令的儲存 |
LoadStore Barriers | 確保Load1資料裝載先於Store2及所有後序的儲存指令重新整理到記憶體 |
StoreLoad Barriers | 確保Store1資料對其他處理器變得可見(指重新整理到記憶體)先於Load2及所有後序裝載指令的裝載。StoreLoad Barriers會使改屏障之前的所有記憶體訪問指令(儲存和裝載指令)完成之後,才執行該屏障之後的記憶體訪問指令 |
- 編譯器、runtime和處理器都必須遵守as-if-serial語義,即不管怎麼重排序,(單執行緒)程式的執行結果不能被改變。
volatile的記憶體語義
- volatile變數自身具有以下特性:
- 可見性:對一個volatile變數的讀,總是能看到(任意執行緒)對這個volatile變數最後的寫入。
- 原子性:對任意單個volatile變數的讀/寫具有原子性,但類似於volatile++這種複合操作不具有原子性。
- volatile寫-讀的記憶體語義:
- 當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體。
- 當讀一個volatile變數時,JMM會把該執行緒對應的本地記憶體置為無效。執行緒接下來將從主記憶體中讀取共享變數。
- JMM記憶體屏障插入策略:
- 在每個volatile寫操作前後分別插入StoreStore、StoreLoad屏障。
- 在每個volatile讀操作後面插入LoadLoad、LoadStore屏障。
鎖的記憶體語義
- 當執行緒獲取鎖時,JMM會把該執行緒對應的本地記憶體置為無效。
- 當執行緒釋放鎖時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體中。
- 公平鎖和非公平鎖的記憶體語義:
- 公平鎖和非公平鎖釋放時,最後都要寫一個volatile變數state。
- 公平鎖獲取時,首先會去讀volatile變數。
- 非公平鎖獲取時,首先會用CAS操作更新volatile變數,這個操作同時具有volatile讀/寫的記憶體語義。
final域的記憶體語義
- 對於final域,編譯器和處理器要遵循兩個重排序規則。
- 在建構函式內對一個final域的寫入,與隨後把這個被構造物件的引用賦值給一個引用變數,這兩個操作之間不能重排序。
- 初次讀一個包含final域的物件的應用,與隨後初次讀這個final域,這兩個操作之間不能重排序。
- 讀final域的重排序規則可以確保:在讀一個物件的final域之前,一定會先讀包含這個final域的物件的引用。
- 寫final域的重排序規則可以確保:在物件引用為任意執行緒可見之前,物件的final域已經被正確初始化。(前提是物件引用不能在建構函式中逸出)
第四章 Java併發程式設計基礎
- 執行緒的優先順序僅僅是一部分決定因素,因為執行緒的切換具有隨機性,而且針對不同的系統而言,優先順序這個概念可能就不存在,其僅僅是決定程式設計的衡量的一個標準。
- 等待/通知的經典範式
- 等待方遵循如下原則:
- 獲取物件的鎖
- 如果條件不滿足,那麼呼叫物件的wait()方法,被通知後仍要檢查條件。
- 條件滿足則執行對應的邏輯。
- 通知方遵循如下原則:
- 獲得物件的鎖。
- 改變條件。
- 通知所有等待在物件上的執行緒。
- 等待方遵循如下原則:
第五章 Java中的鎖
- Lock介面提供的synchronized關鍵字不具備的主要特性
特性 | 描述 |
---|---|
嘗試非阻塞地獲取鎖 | 當前執行緒嘗試獲取鎖,如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取並持有鎖 |
能被中斷地獲取鎖 | 獲取到鎖的執行緒能夠相應中斷,當獲取到鎖的執行緒被中斷時,中斷異常將會被丟擲,同時鎖會被釋放 |
超時獲取鎖 | 在指定的截止時間之前獲取鎖,如果截止時間到了仍舊無法獲取鎖,則返回 |
佇列同步器(AbstractQueuedSynchronizer)
- 同步器提供如下3個方法來訪問或修改同步狀態(int成員變數):
- getState():獲取當前同步狀態
- setState(int newState):設定當前同步狀態
- compareAndSetState(int expect, int update):使用CAS設定當前狀態,該方法能夠保證狀態設定的原子性。
- 同步器可重寫的方法
方法名稱 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 獨佔式獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS設定同步狀態 |
protected boolean tryRelease(int arg) | 獨佔式釋放同步狀態,等待獲取同步狀態的執行緒將有機會獲取同步狀態 |
protected int tryAcquireShared(int arg) | 共享式獲取同步狀態,返回大於等於0的值,表示獲取成功,反之失敗 |
protected boolean tryReleaseShared(int arg) | 共享式釋放同步狀態 |
protected boolean isHeldExclusively() | 當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所獨佔 |
- 同步器提供的模板方法
- 同步佇列及等待佇列的節點屬性型別與名稱以及描述
- 同步佇列中的結點只有當其前驅結點為頭結點時,才嘗試獲取同步狀態。
- 讀寫鎖的同步狀態高16位表示讀狀態、低16位表示寫狀態。
LockSupport
- 當需要阻塞或喚醒一個執行緒的時候,都會使用LockSupport工具類來完成相應工作。LockSupport定義了一組公共靜態方法,這些方法提供了最基本的執行緒阻塞和喚醒功能,而LockSupport也成為了構建同步組建的基礎工具。
- LockSupport提供的阻塞和喚醒方法(其中引數blocker是用來標識當前執行緒在等待的物件,便於問題排查和系統監控)
方法名稱 | 描述 |
---|---|
void park(Object blocker) | 阻塞當前執行緒,如果呼叫unpark方法或者當前執行緒被終端,才能從park方法返回 |
void parkNanos(Object blocker, long nanos) | 阻塞當前執行緒,最長不超過nanos納秒,返回條件在park的基礎上增加了超時返回 |
void parkUntil(Object blocker, long deadline) | 阻塞當前執行緒,知道deadline時間 |
void unpark(Thread thread) | 喚醒處於阻塞狀態的執行緒thread |
第六章 Java併發容器和框架
- ConcurrentHashMap使用鎖分段技術,將資料非誠一段一段地儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問。
- ConcurrentLinkedQueue非阻塞的執行緒安全佇列
阻塞佇列
- 阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。這兩個附加的操作支援阻塞的插入和移除方法。
- 支援阻塞的插入方法:當佇列滿時,佇列會阻塞插入元素的執行緒,知道佇列不滿。
- 支援阻塞的移除方法:當佇列空時,獲取元素的執行緒會等待佇列變為非空。
- 插入和移除操作的4種處理方式
方法/處理方式 | 丟擲異常 | 返回特殊值 | 一直阻塞 | 超時退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除方法 | remove() | poll() | take() | poll(time, unit) |
檢查方法 | element() | peek() | 不可用 | 不可用 |
- 丟擲異常:當佇列滿時,如果再往佇列裡插入元素會丟擲IllegalStateException("Queue full")異常。當佇列空時,從佇列裡獲取元素會丟擲NoSuchElementException異常。
- 返回特殊值:當往佇列插入元素時,會返回元素是否插入成功,成功返回true。如果是移除方法,則從佇列裡取出一個元素,如果沒有則返回null。
- 一直阻塞:當阻塞佇列滿時,如果生產者執行緒往佇列裡put元素,佇列會一直阻塞生產者執行緒,知道佇列可用或者響應中斷退出。當佇列空時,如果消費者執行緒從佇列列take元素,佇列會阻塞消費者執行緒,知道佇列不為空。
- 超時退出:當阻塞佇列滿時,如果生產者執行緒往佇列裡插入元素,佇列會阻塞生產者執行緒一段時間,如果超時則退出。
- JDK7提供了7個阻塞佇列:
- ArrayBlockingQueue:用陣列實現的有界阻塞佇列,按FIFO原則對元素進行排序。
- LinkedBlockingQueue:用連結串列實現的有界阻塞佇列,預設和最大長度為Integer.MAX_VALUE,按FIFO原則對元素進行排序。
- PriorityBlockingQueue:支援優先順序的無界阻塞佇列,預設情況下元素採取自然順序升序排列。不保證同優先順序元素的順序。
- DelayQueue:支援延時獲取元素的無界阻塞佇列。佇列使用PriorityQueue實現,佇列中的元素必須實現Delayed介面,在建立元素時可以指定多久才能從佇列中獲取當前元素。只有在延遲期滿時才能從佇列中提取元素。可應用於:
- 快取系統的設計:用DelayQueue儲存快取元素的有效期,使用一個執行緒迴圈查詢DelayQueue,一旦能從DelayQueue中獲取元素表示快取有效期到了。
- 定時任務排程:使用DelayQueue儲存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行,比如TimerQueue就是使用DelayQueue實現的。
- SynchronousQueue:不儲存元素的阻塞佇列。每一個put操作必須等待一個take操作,否則不能繼續新增元素。
- LinkedTransferQueue:連結串列結構組成的無界阻塞TransferQueue佇列。相對於其他阻塞佇列,LinkedTransferQueue多了tryTransfer和transfer方法。
- transfer方法:如果當前有消費者正在等待接收元素,transfer方法可以把生產者傳入的元素立刻傳給消費者。如果沒有消費者在等待接收元素,則將元素存放在佇列tail節點並等到鈣元素被消費者消費了才返回。
- tryTransfer方法:如果沒有消費者等待接收元素,則立即返回false。
- LinkedBlockingDeque:連結串列結構組成的雙向阻塞佇列。
Fork/Join框架
- Fork/Join框架是一個用於並行執行任務的框架,是一個把大任務分隔成若干個小任務,最終彙總每個小任務結果後得到大人物結果的框架。
- 工作竊取演算法:假如我們需要做一個比較大的任務,我們可以把這個任務分割為若干互不依賴的子任務,為了減少執行緒間的競爭,於是把這些子任務分別放到不同的佇列裡,併為每個佇列建立一個單獨的執行緒來執行佇列裡的任務,執行緒和佇列一一對應,比如A執行緒負責處理A佇列裡的任務。但是有的執行緒會先把自己佇列裡的任務幹完,而其他執行緒對應的佇列裡還有任務等待處理。幹完活的執行緒與其等著,不如去幫其他執行緒幹活,於是它就去其他執行緒的佇列裡竊取一個任務來執行。而在這時它們會訪問同一個佇列,所以為了減少竊取任務執行緒和被竊取任務執行緒之間的競爭,通常會使用雙端佇列,被竊取任務執行緒永遠從雙端佇列的頭部拿任務執行,而竊取任務的執行緒永遠從雙端佇列的尾部拿任務執行。工作竊取演算法的優點是充分利用執行緒進行平行計算,並減少了執行緒間的競爭,其缺點是在某些情況下還是存在競爭,比如雙端佇列裡只有一個任務時。並且消耗了更多的系統資源,比如建立多個執行緒和多個雙端佇列。
- ForkJoinTask:我們要使用ForkJoin框架,必須首先建立一個ForkJoin任務。它提供在任務中執行fork()和join()操作的機制,通常情況下我們不需要直接繼承ForkJoinTask類,而只需要繼承它的子類,Fork/Join框架提供了以下兩個子類:
- RecursiveAction:用於沒有返回結果的任務。
- RecursiveTask :用於有返回結果的任務。
- ForkJoinPool :ForkJoinTask需要通過ForkJoinPool來執行,任務分割出的子任務會新增到當前工作執行緒所維護的雙端佇列中,進入佇列的頭部。當一個工作執行緒的佇列裡暫時沒有任務時,它會隨機從其他工作執行緒的佇列的尾部獲取一個任務。
- ForkJoinTask在執行的時候可能會丟擲異常,但是我們沒辦法在主執行緒裡直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否一件丟擲異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。其中,getException方法返回Throwable物件,如果任務被取消了則返回CancellationException,如果任務沒有完成或者沒有丟擲異常則返回null。
第七章 Java中的13個原子操作類(Ps:認真數了一下發現只有12個)
- 原子更新方式
- 原子更新基本型別
- 原子更新陣列
- 原子更新引用
- 原子更新屬性(欄位) *原子更新基本型別
- AtomicBoolean :原子更新布林型別
- AtomicInteger: 原子更新整型
- AtomicLong: 原子更新長整型
- 原子更新陣列
- AtomicIntegerArray :原子更新整型數組裡的元素
- AtomicLongArray :原子更新長整型數組裡的元素
- AtomicReferenceArray : 原子更新引用型別陣列的元素
- 原子更新引用型別
- AtomicReference :原子更新引用型別
- AtomicReferenceFieldUpdater :原子更新引用型別裡的欄位
- AtomicMarkableReference:原子更新帶有標記位的引用型別。可以原子更新一個布林型別的標記位和應用型別
- 原子更新欄位類
- AtomicIntegerFieldUpdater:原子更新整型的欄位的更新器
- AtomicLongFieldUpdater:原子更新長整型欄位的更新器
- AtomicStampedReference:原子更新帶有版本號的引用型別。該類將整型數值與引用關聯起來,可用於原子的更新資料和資料的版本號,可以解決使用CAS進行原子更新時可能出現的ABA問題。
第八章 Java中的併發工具類
CountDownLatch
- CountDownLatch允許一個或多個執行緒等待其他執行緒完成操作。
- CountDownLatch的建構函式接收一個int型別的引數作為計數器,如果你想等待N個點完成,就傳入N。
- 每次呼叫CountDownLatch的countDown方法時,N就減1,CountDownLatch的await方法會阻塞當前執行緒,知道N變成0。
CyclicBarrier
- CyclicBarrier的作用是讓一組執行緒到達一個屏障(也可以稱之為同步點)時被阻塞,知道最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才能繼續執行。
- CyclicBarrier(int parties)建構函式接收一個int引數用來設定攔截執行緒的數量,還有一個更高階的建構函式CyclicBarrier(int parties, Runnable barrierAction)設定第一個到達屏障的執行緒執行barrierAction。
- getNumberWaiting方法可以獲得CyclicBarrier阻塞的執行緒數量,isBroken()方法用來了解阻塞的執行緒是否被中斷。
Semaphore
- Semaphore(訊號量)用來控制同時訪問特定資源的執行緒數量,通過協調各個執行緒以保證合理的使用公共資源。
- Semaphore(int permits)構造方法接收一個int引數,表示可用的許可證數量。
- 每次執行緒使用Semaphore的acquire()方法獲取一個許可證,用完後呼叫release()方法歸還。
- 其他方法
- intavailablePermits():返回此訊號量中當前可用的許可證數。
- intgetQueueLength():返回正在等待獲取許可證的執行緒數。
- booleanhasQueuedThreads():是否有執行緒正在等待獲取許可證。
- void reducePermits(int reduction):減少reduction個許可證,是個protected方法。
- Collection getQueuedThreads():返回所有等待獲取許可證的執行緒集合,是個protected方法。
Exchanger
- 用於執行緒間交換資料,每兩次執行Exchanger的exchange(V data)方法,則交換兩次執行的資料並返回。可用於校對工作。
第九章 Java中的執行緒池
- 執行緒池的3個好處
- 降低資源消耗:通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
- 提高響應速度:當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
- 提高執行緒的可管理性:執行緒是稀缺資源,如果無限制地建立,不僅會消耗系統資源,還會降低系統的穩定性。使用執行緒池可以進行統一分配、調優和監控。
- 當提交一個新任務到執行緒池時,執行緒池的流程:
- 如果當前執行的執行緒少於corePoolSize,則建立新執行緒來執行任務。
- 如果執行的執行緒等於或多於corePollSize,則將任務加入BlockingQueue。
- 如果無法將任務加入BlockingQueue(佇列已滿),則建立新的執行緒來處理任務。
- 如果建立新執行緒將導致當前執行的執行緒數超過maximumPoolSize,任務將被拒絕,並呼叫RejectedExecutionHandler.rejectedExecution()方法。
執行緒池的構造方法
- ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler),其中:
- corePoolSize(執行緒池的基本大小):當提交一個任務到執行緒池時,執行緒池會建立一個執行緒來執行任務,即使其他空閒的基本執行緒能夠執行新任務也會建立執行緒,知道需要執行的任務數大於執行緒池基本大小。如果呼叫了執行緒池的prestartAllCoreThreads()方法,執行緒池會提前建立並啟動所有基本執行緒。
- runnableTaskQueue(任務佇列):用於儲存等待執行的任務的阻塞佇列。可以選擇ArratBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。
- maximumPoolSize(執行緒池最大數量):執行緒池允許建立的最大執行緒數。
- ThreadFactory:用於設定建立執行緒的工廠,可以通過執行緒工廠給每個創建出來的執行緒設定更有意義的名字。
- RejectedExecutionHandler(飽和策略):當佇列和執行緒池都滿了,說明執行緒池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略預設情況下是AbortPolicy,表示無法處理新任務時丟擲異常。策略有下列幾種:
- AbortPolicy:直接丟擲異常
- CallerRunsPolicy:只用呼叫者所線上程來執行任務。
- DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。
- DiscardPolicy:不處理,丟棄掉。
- 其他應用場景需要實現RejectedExecutionHandler介面自定義。
- keepAliveTime(執行緒活動保持時間):執行緒池的工作執行緒空閒後,保持存活的時間。如果任務多且執行時間短,可以調高存活時間提高執行緒利用率。
- TimeUnit(執行緒活動保持時間的單位)
- 向執行緒池提交任務有兩種方式:
- execute(Runnable command):沒有返回值
- submit(Callable task):返回一個future型別的物件,通過這個物件判斷任務是否執行成功,並通過其get()方法來獲取返回值,該方法會阻塞當前執行緒知道任務完成。
- 執行緒池的關閉有兩種方法:
- shutdown():將執行緒池的狀態設定成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的執行緒。
- shutdownNow():將執行緒池的狀態設定成STOP,然後嘗試停止所有的正在執行或暫停任務的執行緒,並返回等待執行任務的列表。
- 只要呼叫了兩種關閉方法的任一種,isShutdown()方法都會返回true。當且僅當所有任務都關閉,CIA表示執行緒池關閉成功,這是isTerminaed()方法才會返回true。
合理配置執行緒池(設N為CPU個數)
- CPU密集型任務,應配置儘可能少的執行緒,如N+1。
- IO密集型任務,應配置儘可能多的執行緒,如2N。
- 優先順序不同的任務可以考慮使用優先順序佇列priorityBlockingQueue來處理,但優先順序低的任務可能永遠不被執行。
- 使用有界佇列能增加系統的穩定性和預警性,避免佇列越來越多撐滿記憶體,導致系統不可用。
執行緒池的監控
- 監控執行緒池的時候可以使用以下屬性:
- taskCount:執行緒池需要執行的任務數量。
- completedTaskCount:執行緒池在執行過程中已完成的任務數量,小於或等於taskCount。
- largestPoolSize:執行緒池裡曾經建立過的最大執行緒數量。通過這個資料可以知道執行緒池是否曾經滿過。
- getPoolSize:執行緒池的執行緒數量。如果執行緒池不銷燬的話,執行緒池裡的執行緒不會自動銷燬,所以這個大小隻增不減。
- getActiveCount:獲取活動的執行緒數。
- 可以通過繼承執行緒池來自定義執行緒池,重寫執行緒池的beforeExecute, afterExecute和terminated方法,也可以在任務執行前後和執行緒池關閉前執行一些程式碼來進行監控。例如,監控任務的平均執行時間、最大執行時間和最小執行時間等。這幾個方法線上程池裡都是空方法。
第十章 Executor框架
- 工廠類Executors可建立3種類型的ThreadPoolExecutor:
- FixedThreadPool:可重用固定執行緒數的執行緒池。
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
- SingleThreadExecutor:使用單個worker執行緒的Executor。
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
- CachedThreadPool:會根據需要建立新執行緒的執行緒池。
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnbalbe>())
- FixedThreadPool:可重用固定執行緒數的執行緒池。
- 工廠類Executors可建立2種類型的ScheduledThreadPoolExecutor:
- ScheduledThreadPoolExecutor:包含若干個執行緒的ScheduledThreadPoolExecutor。
- SingleThreadScheduledExecutor:只包含一個執行緒的ScheduledThreadPoolExecutor。
第十一章 Java併發程式設計實踐
- 略