《Java併發編程的藝術》筆記
第一章 併發編程的挑戰
第二章 Java併發機制的底層實現原理
volatile的兩條實現原則:java
- 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實現原子操做的三大問題高併發
- 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>())
工廠類Executors可建立2種類型的ScheduledThreadPoolExecutor:
- ScheduledThreadPoolExecutor:包含若干個線程的ScheduledThreadPoolExecutor。
- SingleThreadScheduledExecutor:只包含一個線程的ScheduledThreadPoolExecutor。
第十一章 Java併發編程實踐