Java並發編程知識點總結Volatile、Synchronized、Lock實現原理
- 2019 年 10 月 3 日
- 筆記
Volatile關鍵字及其實現原理
在多執行緒並發編程中,Volatile可以理解為輕量級的Synchronized,用volatile關鍵字聲明的變數,叫做共享變數,其保證了變數的“可見性”以及“有序性”。可見性的意思是當一個執行緒修改一個共享變數時,另外一個執行緒能讀到這個修改的值。可見性是由Java記憶體模型保證的(底層還是通過記憶體屏障實現的),即某個執行緒改變共享變數的值之後,會立即同步到主記憶體,執行緒每次使用共享變數的時候都先從記憶體中讀取刷新它的值;而有序性是通過“記憶體屏障”實現的,通過禁止指令重排序,從而使得某些程式碼能以一定的順序執行。
但是Volatile關鍵字不能保證對共享變數操作的“原子性”,比如自增操作(i++)就不是原子操作,其可以分解為:①從記憶體中讀取i的值,②對i進行自增操作,③將i的值寫入到記憶體中;因此volatile關鍵字也不能完全保證多執行緒下的安全性。
在了解volatile關鍵字的原理之前,首先來看一下與其實現原理相關的CPU術語與說明。
圖1.與volatile實現原理相關的相關CPU術語與說明
通過對聲明了volatile變數的Java語句進行反編譯可以發現,有volatile變數修飾的共享變數進行寫操作的時候會多出第二行彙編程式碼,其中Lock前綴的指令在多核處理器下會引發兩件事情:
- 將當前處理器快取行的數據寫回到系統記憶體;
- 這個寫回記憶體的操作使得其他處理器快取了該記憶體地址的數據無效。
這裡的快取指的是CPU的高速快取,電腦為了提高處理的速度,處理器不直接和記憶體進行通訊,而是將系統記憶體中的數據讀取到高速快取(L1、L2等)之後再進行操作,但操作不知道何時會寫到記憶體。
如果對聲明了volatile的變數進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變數所在快取行的數據寫回到系統記憶體。但是,就算寫回到記憶體,如果其他處理器快取的值還是舊的,再執行計算操作還是會有問題。所以,在多處理器下,為了保證各個處理器的快取是一致的,就會實現快取一致性協議,每個處理器通過嗅探在匯流排上傳播的數據來檢查自己快取的值是不是過期了,當處理器發現自己快取行對應的記憶體地址被修改,就會將當前處理器的快取行設置成無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統記憶體中把數據讀到處理器快取里。
圖2.聲明了volatile變數的Java語句反編譯結果
Volatile記憶體語義的實現
為了實現Volatile的記憶體語義,JMM會分別限制編譯器重排序和處理器重排序。表3-5是JMM針對編譯器制定的volatile重排序規則表。
- 當第二個操作是volatile寫時,不管第一個操作是什麼,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之後。
- 當第一個操作是volatile讀時,不管第二個操作是什麼,都不能重排序。這個規則確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。、
- 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。
在編譯器生成位元組碼的時候,會在指令序列中通過插入記憶體屏障來禁止特定類型的處理器重排序。JMM基於保守策略插入記憶體屏障。
- 在每個volatile寫操作的前面插入一個StoreStore屏障;
- 在每個volatile寫操作的後面插入一個StoreLoad屏障;
- 在每個volatile讀操作的後面插入一個LoadLoad屏障;
- 在每個volatile讀操作的後面插入一個LoadStore屏障。
Synchronized及其實現原理
在多執行緒並發編程中synchronized一直是元老級角色,很多人稱其為重量級鎖,Java SE 1.6 對Synchronized進行了大量的優化,包括引入偏向鎖和輕量級鎖,減少了獲取鎖和釋放鎖帶來的性能消耗,使得Synchronized不再那麼重量級。
Synchronized實現同步的基礎是:Java中每個變數都可以作為鎖. Synchronized在日常的使用中,主要有以下三種形式:
- 修飾普通方法,鎖的是當前實例變數(this);
- 修飾靜態(類)方法,鎖的是當前類的Class對象;
- 修飾同步方法快,鎖的是Synchronized括弧中的對象。
當執行緒訪問同步方法或者程式碼塊時,其必須先獲得鎖,在退出或者拋出異常時釋放鎖。JVM基於進入和退出Monitor對象來實現方法同步和程式碼塊同步,但兩者的實現細節不一樣。程式碼塊同步是使用monitorenter和monitorexit指令實現的,而方法同步是使用另外一種方式實現的(隱式調用這兩個指令)。
monitorenter指令是在編譯後插入到同步程式碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,兩兩配對。任何對象都有一個monitor與之關聯,當一個monitor被持有後,它將處於鎖定狀態。執行緒執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖。
Synchronized是可重入的,所謂可重入的意思就是,當某個執行緒獲得鎖時,再次請求進入有同一個鎖修飾的程式碼塊或者方法時,操作會獲得成功。其中的其實原理課以簡單概括為moniter對象維護了一個執行緒持有者變數和一個鎖計數器,每當有執行緒嘗試獲取鎖的時候,如果當前鎖持有者為空,那麼該執行緒將成功獲得鎖,同時計數器執行+1操作;如果當前鎖持有者不為空,那麼會先檢查鎖的持有者跟當前請求鎖的執行緒是否是同一個,如果不是同一個,那麼請求鎖失敗,該執行緒會進入阻塞狀態,如果當前請求獲取鎖的執行緒與鎖持有者是相同的,那麼獲取鎖的請求會成功,且計數器會執行自增操作,沒退出一個同步方法或者執行程式碼塊,計數器會-1,直到為0,此時鎖處於空閑狀態。
Java對象頭
Synchronized所用的鎖是存在Java對象頭裡面的,如果對象是數組類型,虛擬機會用3個字寬(Word)來存儲對象頭,否則用2個字寬來存儲對象頭。在32位虛擬機中,1字寬=4位元組=32bit.
圖3.Java對象頭的長度
Java對象頭裡的Mark Word里默認存儲對象的HashCode、分代年齡和鎖標記位。32位JVM的Mark Word的默認存儲結構如下圖所示:
圖4.Java對象頭存儲結構
在運行期間Mark Word里存儲的數據會隨著鎖標誌位的變化而變化:
鎖的升級與對比
在Java SE 1.6中鎖一共有四種狀態,從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。
1.偏向鎖
大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。當一個執行緒訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的執行緒ID,以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前執行緒。
偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執行的位元組碼)。它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,如果執行緒不處於活動狀態,則將對象頭設置成無鎖狀態;如果執行緒仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼重新偏向於其他執行緒,要麼恢復到無鎖或者標記對象不適合作為偏向鎖,最後喚醒暫停的執行緒。圖2-1中的執行緒1演示了偏向鎖初始化的流程,執行緒2演示了偏向鎖撤銷的流程。
2. 輕量級鎖
(1)輕量級鎖加鎖
執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。
(2)輕量級鎖解鎖
輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。圖2-2是兩個執行緒同時爭奪鎖,導致鎖膨脹的流程圖。
因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的執行緒被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他執行緒試圖獲取鎖時,都會被阻塞住,當持有鎖的執行緒釋放鎖之後會喚醒這些執行緒,被喚醒的執行緒就會進行新一輪的奪鎖之爭。
Happens-before簡介
從JDK 5開始,Java使用新的JSR-133記憶體模型(除非特別說明,本文針對的都是JSR-133記憶體模型)。JSR-133使用happens-before的概念來闡述操作之間的記憶體可見性。在JMM中,如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須要存在happens-before關係。這裡提到的兩個操作既可以是在一個執行緒之內,也可以是在不同執行緒之間。與程式設計師密切相關的happens-before規則如下。
- 程式順序規則:一個執行緒中的每個操作,happens-before於該執行緒中的任意後續操作。
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
- volatile變數規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
- 傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C。
兩個操作之間具有happens-before關係,並不意味著前一個操作必須要在後一個操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。
as-if-serial語義
as-if-serial語義的意思是:不管怎麼重排序(編譯器和處理器為了提高並行度),(單執行緒)程式的執行結果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語義。為了遵守as-if-serial語義,編譯器和處理器不會對存在數據依賴關係的操作做重排序,因為這種重排序會改變執行結果。但是,如果操作之間不存在數據依賴關係,這些操作就可能被編譯器和處理器重排序.
Java多執行緒
執行緒:
執行緒是系統調度的基本單位。每個執行緒都有自己的程式計數器、Java虛擬機棧、本地方法棧。
等待/通知的相關方法
等待/通知的相關方法是任意Java對象都具備的,因為這些方法被定義在所有對象的超類java.lang.Object上,方法和描述如表4-2所示。
Objecte類擁有的方法有:hashcode、getClass、wait、notify、notifyAll、equals、clone、finalize、toString方法。
調用wait()、notify()以及notifyAll()時需要注意的細節,如下:
- 使用wait()、notify()和notifyAll()時需要先對調用對象加鎖。
- 調用wait()方法後,執行緒狀態由RUNNING變為WAITING,釋放所佔用的對象鎖,並將當前執行緒放置到對象的等待隊列。
- notify()或notifyAll()方法調用後,等待執行緒依舊不會從wait()返回,需要調用notify()或notifAll()的執行緒釋放鎖之後,等待執行緒才有機會從wait()返回。
- notify()方法將等待隊列中的一個等待執行緒從等待隊列中移到同步隊列中,而notifyAll()方法則是將等待隊列中所有的執行緒全部移到同步隊列,被移動的執行緒狀態由WAITING 變為BLOCKED。
- 從wait()方法返回的前提是獲得了調用對象的鎖。
在圖4-3中,WaitThread首先獲取了對象的鎖,然後調用對象的wait()方法,從而放棄了鎖並進入了對象的等待隊列WaitQueue中,進入等待狀態。由於WaitThread釋放了對象的鎖,NotifyThread隨後獲取了對象的鎖,並調用對象的notify()方法,將WaitThread從WaitQueue移到SynchronizedQueue中,此時WaitThread的狀態變為阻塞狀態。NotifyThread釋放了鎖之後,WaitThread再次獲取到鎖並從wait()方法返回繼續執行。
Lock介面
Java SE 5之後,並發包Java.util.concurrent引入了Lock介面來實現鎖功能,它與synchronized的同步功能相類似,但是在使用時需要顯式地獲取鎖和釋放鎖(在finally塊中釋放鎖,目的是保證在獲取到鎖之後,最終能夠被釋放),其次Lock還擁有可中斷地獲取鎖以及超時獲取鎖等Synchronized不具備的功能。
Lock是一個介面,它定義了鎖獲取和釋放的基本操作,Lock的API如表5-2所示。
加鎖調用鏈為Lock.lock()->Lock.acquire()->Sync.tryAcquire();解鎖調用鏈為Lock.unlock()->Lock.release()->Sync.tryRelease();其中Sync是Lock的一個靜態內部類,Sync繼承自AQS,.acquire()和.release()是AQS的模板方法,最終調用由Sync重寫的tryAcquire()和tryRelease()方法;
隊列同步器AQS(Abstract Queued Synchronizer)
隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變數表示同步狀態,通過內置的FIFO隊列來完成資源獲取執行緒的排隊工作。
同步器的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了要對同步狀態進行更改,這時就需要使用同步器提供的3個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操作,因為它們能夠保證狀態的改變是安全的。同步器自身沒有實現任何同步介面,它僅僅是定義了若干同步狀態獲取和釋放的方法來供自定義同步組件使用,同步器既可以支援獨佔式地獲取同步狀態,也可以支援共享式地獲取同步狀態,這樣就可以方便實現不同類型的同步組件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
顧名思義,獨佔鎖就是在同一時刻只能有一個執行緒獲取到鎖,而其他獲取鎖的執行緒只能處於同步隊列中等待,只有獲取鎖的執行緒釋放了鎖,後繼的執行緒才能夠獲取鎖。
實現自定義同步組件時,將會調用同步器提供的模板方法,這些(部分)模板方法與描述如表5-4所示。
隊列同步器AQS的實現分析
AQS主要包括:同步隊列、獨佔式同步狀態獲取與釋放、共享式同步狀態獲取與釋放以及超時獲取同步狀態等同步器的核心數據結構與模板方法。
- 同步隊列
同步器依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理,當前執行緒獲取同步狀態失敗時,同步器會將當前執行緒以及等待狀態等資訊構造成為一個節點(Node)並將其加入同步隊列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點中的執行緒喚醒,使其再次嘗試獲取同步狀態。
獨佔式同步狀態獲取流程,也就是acquire(int arg)方法調用流程,如圖5-5所示。
分析了獨佔式同步狀態獲取和釋放過程後,適當做個總結:在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的執行緒都會被加入到隊列中並在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節點為頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease(int arg)方法釋放同步狀態,然後喚醒頭節點的後繼節點。
獨佔式超時獲取同步狀態doAcquireNanos(int arg,long nanosTimeout)和獨佔式獲取同步狀態acquire(int args)在流程上非常相似,其主要區別在於未獲取到同步狀態時的處理邏輯。acquire(int args)在未獲取到同步狀態時,將會使當前執行緒一直處於等待狀態,而doAcquireNanos(int arg,long nanosTimeout)會使當前執行緒等待nanosTimeout納秒,如果當前執行緒在nanosTimeout納秒內沒有獲取到同步狀態,將會從等待邏輯中自動返回。
共享式同步狀態獲取與釋放
共享式獲取與獨佔式獲取最主要的區別在於同一時刻能否有多個執行緒同時獲取到同步狀態。
LockSupport工具
當需要阻塞或喚醒一個執行緒的時候,都會使用LockSupport工具類來完成相應工作。LockSupport定義了一組的公共靜態方法,這些方法提供了最基本的執行緒阻塞和喚醒功能,而LockSupport也成為構建同步組件的基礎工具。
LockSupport定義了一組以park開頭的方法用來阻塞當前執行緒,以及unpark(Thread thread)方法來喚醒一個被阻塞的執行緒。
Condition介面
任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,可以實現等待/通知模式。Condition介面也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。
阻塞隊列
阻塞隊列(BlockingQueue)是一個支援兩個附加操作的隊列。這兩個附加的操作支援阻塞的插入和移除方法。
- 支援阻塞的插入方法:意思是當隊列滿時,隊列會阻塞插入元素的執行緒,直到隊列不滿。
- 支援阻塞的移除方法:意思是在隊列為空時,獲取元素的執行緒會等待隊列變為非空。
阻塞隊列常用於生產者和消費者的場景,生產者是向隊列里添加元素的執行緒,消費者是從隊列里取元素的執行緒。阻塞隊列就是生產者用來存放元素、消費者用來獲取元素的容器。
Java裡面的7個阻塞隊列
JDK 7提供了7個阻塞隊列,如下。
- ·ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列,默認非公平,FIFO。
- ·LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列,默認和最大長度為
- Integer.MAX_VALUE , FIFO。
- ·PriorityBlockingQueue:一個支援優先順序排序的無界阻塞隊列。
- ·DelayQueue:一個使用優先順序隊列實現的無界阻塞隊列,支援延時獲取元素。
- ·SynchronousQueue:一個不存儲元素的阻塞隊列,默認非公平。
- ·LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
- ·LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
Java中的13個原子操作類
Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、性能高效、執行緒安全地更新一個變數的方式。
因為變數的類型有很多種,所以在Atomic包里一共提供了13個類,屬於4種類型的原子更新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(欄位)。Atomic包里的類基本都是使用Unsafe實現的包裝類。
ActomicInteger/AtomicBoolean/AtomicLong/AtomicReference
getAndIncreasement()/getAndSet()/compareAndSet)方法
CountDownLatch/CyclicBarrier/Semaphore
用法:CountDownLatch c=new CountDownLatch(2);
CountDownLatch內部有個靜態內部類繼承自AQS,每個執行緒執行到某個位置後,執行countdownlatch.countDown()方法使得數字減一,減到0為止;所有執行緒都會阻塞在c.await()直到c.countDown()減到0為止,然後繼續執行;
用法:CyclicBarrier c = new CyclicBarrier(2);
CyclicBarrier中,每個執行緒執行到某個位置時,調用c.awit()通知主執行緒或者某個特定執行緒表示自己已經到達循環屏障了,當達到循環屏障的執行緒數量等於構造函數中的數字時;所有執行緒都再繼續往下執行;
CyclicBarrier相較於CountDownLatch來說可以實現一些更高級的功能,比如可以重置計數器,比如可以知道阻塞的執行緒數,比如可以知道哪些執行緒被中斷,比如可以加入別的任務,在所有執行緒都到達屏障時優先執行該任務。
Semaphore
Semaphore(訊號量)是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。多年以來,我都覺得從字面上很難理解Semaphore所表達的含義,只能把它比作是控制流量的紅綠燈。比如××馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入××馬路,但是如果前一百輛中有5輛車已經離開了××馬路,那麼後面就允許有5輛車駛入馬路,這個例子里說的車就是執行緒,駛入馬路就表示執行緒在執行,離開馬路就表示執行緒執行完成,看見紅燈就表示執行緒被阻塞,不能執行。
Semaphore可以用於做流量控制,特別是公用資源有限的應用場景,比如資料庫連接。假如有一個需求,要讀取幾萬個文件的數據,因為都是IO密集型任務,我們可以啟動幾十個執行緒並發地讀取,但是如果讀到記憶體後,還需要存儲到資料庫中,而資料庫的連接數只有10個,這時我們必須控制只有10個執行緒同時獲取資料庫連接保存數據,否則會報錯無法獲取資料庫連接。這個時候,就可以使用Semaphore來做流量控制,如程式碼清單8-7所示。
Java中的執行緒池
使用執行緒池的好處:
- 降低資源消耗;通過重複利用已經創建的執行緒降低執行緒創建和銷毀造成的消耗。
- 提高響應速度;當任務達到時,任務可以不需要等到執行緒創建就能立即執行。
- 提高執行緒的可管理性;執行緒不能無限制地創建,否則會消耗系統資源以及降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控。
利用Executors.newFixedThreadPool()創建一個含有N個執行緒的執行緒池;其中工廠方法調用ThreadPoolExecutor的構造方法,創建了一個核心執行緒數和最大執行緒數都是N的執行緒池,該執行緒池的keepalivedTime設置為0L,意味著,非核心執行緒一旦閑暇就會被終止,同時其採用LinkedBlockingQueue(一種以鏈表為基礎的遊街阻塞隊列,不傳參數默認創建Integer。MAX_VALUE大小的阻塞隊列)
1 //利用Executors.newFixedThreadPool 2 3 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { 4 return new ThreadPoolExecutor(nThreads, nThreads, 5 0L, TimeUnit.MILLISECONDS, 6 new LinkedBlockingQueue<Runnable>(), 7 threadFactory); 8 }
利用Executors.newSingleThreadExecutor()創建一個大小為1的執行緒池;其最終調用的是ThreadPoolExecutor的構造方法創建了一個核心執行緒數和最大執行緒數都為1的執行緒池,keepaliveTime設置為0L,意味著非核心執行緒一旦閑下來就會被終止,其採用LinkedBlockingQueue作為阻塞隊列。
1 public static ExecutorService newSingleThreadExecutor() { 2 return new FinalizableDelegatedExecutorService 3 (new ThreadPoolExecutor(1, 1, 4 0L, TimeUnit.MILLISECONDS, 5 new LinkedBlockingQueue<Runnable>())); 6 }
利用Executors.newCachedThreadPool()創建一個核心執行緒數為0,最大執行緒數為Integer.MAX_VALUE,keepAlivedTime為60s,並且使用SynchronousQueue阻塞隊列來做任務隊列().CachedThreadPool使用沒有容量的SynchronousQueue作為執行緒池的工作隊列,但CachedThreadPool的maximumPool是無界的。這意味著,如果主執行緒提交任務的速度高於maximumPool中執行緒處理任務的速度時,CachedThreadPool會不斷創建新執行緒。極端情況下,CachedThreadPool會因為創建過多執行緒而耗盡CPU和記憶體資源。
1 public static ExecutorService newCachedThreadPool() { 2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3 60L, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>()); 5 }