《Java併發編程的藝術》筆記

轉自:https://www.cnblogs.com/corvey/p/8478801.html

第一章 併發編程的挑戰

第二章 Java併發機制的底層實現原理

volatile的兩條實現原則:

  1. Lock前綴指令會引起處理器緩存回寫到內存
  2. 一個處理器的緩存回寫到內存會導致其他處理器的緩存無效。

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實現原子操作的三大問題

  1. ABA問題。一個值從A變成B又變成A,使用CAS檢查時以爲沒有變化,但實際上卻變化了。解決思路是使用版本號。
  2. 循環時長長,開銷大。
  3. 只能保證一個共享變量的原子操作。(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域,編譯器和處理器要遵循兩個重排序規則:

  1. 在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
  2. 初次讀一個包含final域的對象的應用,與隨後初次讀這個final域,這兩個操作之間不能重排序。
  • 讀final域的重排序規則可以確保:在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。
  • 寫final域的重排序規則可以確保:在對象引用爲任意線程可見之前,對象的final域已經被正確初始化。(前提是對象引用不能在構造函數中逸出

第四章 Java併發編程基礎

  • 線程的優先級僅僅是一部分決定因素,因爲線程的切換具有隨機性,而且針對不同的系統而言,優先級這個概念可能就不存在,其僅僅是決定程序設計的衡量的一個標準。

等待/通知的經典範式

  • 等待方遵循如下原則:
    1. 獲取對象的鎖
    2. 如果條件不滿足,那麼調用對象的wait()方法,被通知後仍要檢查條件。
    3. 條件滿足則執行對應的邏輯。
  • 通知方遵循如下原則:
    1. 獲得對象的鎖。
    2. 改變條件。
    3. 通知所有等待在對象上的線程。

第五章 Java中的鎖

Lock接口提供的synchronized關鍵字不具備的主要特性

特性 描述
嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取並持有鎖
能被中斷地獲取鎖 獲取到鎖的線程能夠相應中斷,當獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放
超時獲取鎖 在指定的截止時間之前獲取鎖,如果截止時間到了仍舊無法獲取鎖,則返回

隊列同步器(AbstractQueuedSynchronizer)

同步器提供如下3個方法來訪問或修改同步狀態(int成員變量):

  1. getState():獲取當前同步狀態
  2. setState(int newState):設置當前同步狀態
  3. 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)是一個支持兩個附加操作的隊列。這兩個附加的操作支持阻塞的插入和移除方法。
    1. 支持阻塞的插入方法:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。
    2. 支持阻塞的移除方法:當隊列空時,獲取元素的線程會等待隊列變爲非空。
  • 插入和移除操作的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個阻塞隊列:

  1. ArrayBlockingQueue:用數組實現的有界阻塞隊列,按FIFO原則對元素進行排序。
  2. LinkedBlockingQueue:用鏈表實現的有界阻塞隊列,默認和最大長度爲Integer.MAX_VALUE,按FIFO原則對元素進行排序。
  3. PriorityBlockingQueue:支持優先級的無界阻塞隊列,默認情況下元素採取自然順序升序排列。不保證同優先級元素的順序。
  4. DelayQueue:支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue實現,隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。可應用於:
    • 緩存系統的設計:用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue,一旦能從DelayQueue中獲取元素表示緩存有效期到了。
    • 定時任務調度:使用DelayQueue保存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行,比如TimerQueue就是使用DelayQueue實現的。
  5. SynchronousQueue:不存儲元素的阻塞隊列。每一個put操作必須等待一個take操作,否則不能繼續添加元素。
  6. LinkedTransferQueue:鏈表結構組成的無界阻塞TransferQueue隊列。相對於其他阻塞隊列,LinkedTransferQueue多了tryTransfer和transfer方法。
    • transfer方法:如果當前有消費者正在等待接收元素,transfer方法可以把生產者傳入的元素立刻傳給消費者。如果沒有消費者在等待接收元素,則將元素存放在隊列tail節點並等到鈣元素被消費者消費了才返回。
    • tryTransfer方法:如果沒有消費者等待接收元素,則立即返回false。
  7. 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個好處:

  1. 降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
  2. 提高響應速度:當任務到達時,任務可以不需要等到線程創建就能立即執行。
  3. 提高線程的可管理性:線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性。使用線程池可以進行統一分配、調優和監控。

當提交一個新任務到線程池時,線程池的流程:

  1. 如果當前運行的線程少於corePoolSize,則創建新線程來執行任務。
  2. 如果運行的線程等於或多於corePollSize,則將任務加入BlockingQueue。
  3. 如果無法將任務加入BlockingQueue(隊列已滿),則創建新的線程來處理任務。
  4. 如果創建新線程將導致當前運行的線程數超過maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。

線程池的構造方法

ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler),其中:

  1. corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,直到需要執行的任務數大於線程池基本大小。如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有基本線程。
  2. runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。可以選擇ArratBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。
  3. maximumPoolSize(線程池最大數量):線程池允許創建的最大線程數。
  4. ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字。
  5. RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。策略有下列幾種:
    • AbortPolicy:直接拋出異常
    • CallerRunsPolicy:只用調用者所在線程來運行任務。
    • DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
    • DiscardPolicy:不處理,丟棄掉。
    • 其他應用場景需要實現RejectedExecutionHandler接口自定義。
  6. keepAliveTime(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。如果任務多且執行時間短,可以調高存活時間提高線程利用率。
  7. TimeUnit(線程活動保持時間的單位)

向線程池提交任務有兩種方式:

  1. execute(Runnable command):沒有返回值
  2. submit(Callable task):返回一個future類型的對象,通過這個對象判斷任務是否執行成功,並通過其get()方法來獲取返回值,該方法會阻塞當前線程直到任務完成。

線程池的關閉有兩種方法:

  1. shutdown():將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。
  2. 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>())

工廠類Executors可創建2種類型的ScheduledThreadPoolExecutor:

  • ScheduledThreadPoolExecutor:包含若干個線程的ScheduledThreadPoolExecutor。
  • SingleThreadScheduledExecutor:只包含一個線程的ScheduledThreadPoolExecutor。

第十一章 Java併發編程實踐

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章