1. 程式人生 > 其它 >一文搞定那些難纏的併發面試題

一文搞定那些難纏的併發面試題

1、Object 的 wait()和notify() 方法

下圖為執行緒狀態的圖:

Object 物件中的 wait()和notify()是用來實現實現等待 / 通知模式。其中等待狀態和阻塞狀態是不同的。等待狀態的執行緒可以通過notify() 方法喚醒並繼續執行,而阻塞狀態的執行緒則是等待獲取新的鎖。

  • 呼叫 wait()方法後,當前執行緒會進入等待狀態,直到其他執行緒呼叫notify()或notifyAll() 來喚醒。

  • 呼叫 notify() 方法後,可以喚醒正在等待的單一執行緒。

相關文章參考

再談notify和notifyAll的區別和相同

2、併發特性 - 原子性、有序性、可見性

  • 原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

  • 可見性:指當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。

  • 有序性:即程式執行的順序按照程式碼的先後順序執行,不進行指令重排列。

3、synchronized 實現原理?

synchronized 可以保證方法或者程式碼塊在執行時,同一時刻只有一個程序可以訪問,同時它還可以保證共享變數的記憶體可見性。

Java 中每一個物件都可以作為鎖,這是 synchronized 實現同步的基礎:

  • 普通同步方法,鎖是當前例項物件

  • 靜態同步方法,鎖是當前類的 class 物件

  • 同步方法塊,鎖是括號裡面的物件

同步程式碼塊:monitorenter 指令插入到同步程式碼塊的開始位置,monitorexit指令插入到同步程式碼塊的結束位置,JVM 需要保證每一個monitorenter都有一個monitorexit與之相對應。任何物件都有一個 Monitor 與之相關聯,當且一個 Monitor 被持有之後,他將處於鎖定狀態。執行緒執行到monitorenter 指令時,將會嘗試獲取物件所對應的 Monitor 所有權,即嘗試獲取物件的鎖。

同步方法:synchronized 方法則會被翻譯成普通的方法呼叫和返回指令如:invokevirtual、areturn指令,在 VM 位元組碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在 Class 檔案的方法表中將該方法的access_flags欄位中的synchronized 標誌位置設定為 1,表示該方法是同步方法,並使用呼叫該方法的物件或該方法所屬的 Class 在 JVM 的內部物件表示 Klass 作為鎖物件。

synchronized 是重量級鎖,在 JDK1.6 中進行優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

相關文章參考

死磕Synchronized底層實現--概論

死磕Synchronized底層實現--偏向鎖

死磕Synchronized底層實現--輕量級鎖

死磕Synchronized底層實現--重量級鎖

4、volatile 的實現原理?

volatile 是輕量級的鎖,它不會引起執行緒上下文的切換和排程。

  • volatile可見性:對一個volatile 的讀,總可以看到對這個變數最終的寫。

  • volatile 原子性:volatile對單個讀 / 寫具有原子性(32 位 Long、Double),但是複合操作除外,例如i++ 。

  • JVM 底層採用“記憶體屏障”來實現 volatile 語義,防止指令重排序。

volatile 經常用於兩個兩個場景:狀態標記變數、Double Check 。

相關文章參考

Java併發程式設計:volatile關鍵字解析

5、Java 記憶體模型(JMM)

JMM 規定了執行緒的工作記憶體和主記憶體的互動關係,以及執行緒之間的可見性和程式的執行順序。

  • 一方面,要為程式設計師提供足夠強的記憶體可見性保證。

  • 另一方面,對編譯器和處理器的限制要儘可能地放鬆。JMM 對程式設計師遮蔽了 CPU 以及 OS 記憶體的使用問題,能夠使程式在不同的 CPU 和 OS 記憶體上都能夠達到預期的效果。

Java 採用記憶體共享的模式來實現執行緒之間的通訊。編譯器和處理器可以對程式進行重排序優化處理,但是需要遵守一些規則,不能隨意重排序。

在併發程式設計模式中,勢必會遇到上面三個概念:
  • 原子性:一個操作或者多個操作要麼全部執行要麼全部不執行。

  • 可見性:當多個執行緒同時訪問一個共享變數時,如果其中某個執行緒更改了該共享變數,其他執行緒應該可以立刻看到這個改變。

  • 有序性:程式的執行要按照程式碼的先後順序執行。

通過 volatile、synchronized、final、concurrent 包等 實現。

相關文章參考

深入理解 Java 虛擬機器【1】JVM 記憶體模型

6、有關佇列 AQS 佇列同步器

AQS 是構建鎖或者其他同步元件的基礎框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等), 包含了實現同步器的細節(獲取同步狀態、FIFO 同步佇列)。AQS 的主要使用方式是繼承,子類通過繼承同步器,並實現它的抽象方法來管理同步狀態。

維護一個同步狀態 state。當 state > 0時,表示已經獲取了鎖;當state = 0 時,表示釋放了鎖。

AQS 通過內建的 FIFO 同步佇列來完成資源獲取執行緒的排隊工作:

  • 如果當前執行緒獲取同步狀態失敗(鎖)時,AQS 則會將當前執行緒以及等待狀態等資訊構造成一個節點(Node)並將其加入同步佇列,同時會阻塞當前執行緒

  • 當同步狀態釋放時,則會把節點中的執行緒喚醒,使其再次嘗試獲取同步狀態。

AQS 內部維護的是** CLH 雙向同步佇列**

相關文章參考

AbstractQueuedSynchronizer原始碼分析之條件佇列

7、鎖的特性

可重入鎖:指的是在一個執行緒中可以多次獲取同一把鎖。 ReentrantLock 和 synchronized 都是可重入鎖。

可中斷鎖:顧名思義,就是可以相應中斷的鎖。synchronized 就不是可中斷鎖,而 Lock 是可中斷鎖。

公平鎖:即儘量以請求鎖的順序來獲取鎖。synchronized 是非公平鎖,ReentrantLock 和 ReentrantReadWriteLock,它預設情況下是非公平鎖,但是可以設定為公平鎖。

相關文章參考

Java多執行緒程式設計 — 鎖優化

併發程式設計之死鎖解析

Java讀寫鎖實現原理

8、ReentrantLock 鎖

ReentrantLock,可重入鎖,是一種遞迴無阻塞的同步機制。它可以等同於 synchronized的使用,但是 ReentrantLock 提供了比synchronized 更強大、靈活的鎖機制,可以減少死鎖發生的概率。

  • ReentrantLock 實現 Lock 介面,基於內部的 Sync 實現。

  • Sync 實現 AQS ,提供了 FairSync 和 NonFairSync 兩種實現。

Condition

Condition 和 Lock 一起使用以實現等待/通知模式,通過 await()和singnal() 來阻塞和喚醒執行緒。

Condition 是一種廣義上的條件佇列。他為執行緒提供了一種更為靈活的等待 / 通知模式,執行緒在呼叫 await 方法後執行掛起操作,直到執行緒等待的某個條件為真時才會被喚醒。Condition 必須要配合 Lock 一起使用,因為對共享狀態變數的訪問發生在多執行緒環境下。一個 Condition 的例項必須與一個 Lock 繫結,因此 Condition 一般都是作為 Lock 的內部實現。

相關文章參考

ReentrantLock原始碼分析

9、ReentrantReadWriteLock

讀寫鎖維護著一對鎖,一個讀鎖和一個寫鎖。通過分離讀鎖和寫鎖,使得併發性比一般的排他鎖有了較大的提升:

  • 在同一時間,可以允許多個讀執行緒同時訪問。

  • 但是,在寫執行緒訪問時,所有讀執行緒和寫執行緒都會被阻塞。

讀寫鎖的主要特性:

  • 公平性:支援公平性和非公平性。

  • 重入性:支援重入。讀寫鎖最多支援 65535 個遞迴寫入鎖和 65535 個遞迴讀取鎖。

  • 鎖降級:遵循獲取寫鎖,再獲取讀鎖,最後釋放寫鎖的次序,如此寫鎖能夠降級成為讀鎖。

ReentrantReadWriteLock 實現 ReadWriteLock 介面,可重入的讀寫鎖實現類。

在同步狀態上,為了表示兩把鎖,將一個 32 位整型分為高 16 位和低 16 位,分別表示讀和寫的狀態

10、Synchronized 和 Lock 的區別

  • Lock 是一個介面,而 synchronized 是 Java 中的關鍵字,synchronized 是內建的語言實現;

  • synchronized 在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而 Lock 在發生異常時,如果沒有主動通過 unLock() 去釋放鎖,則很可能造成死鎖現象,因此使用 Lock 時需要在 finally 塊中釋放鎖;

  • Lock 可以讓等待鎖的執行緒響應中斷,而 synchronized 卻不行,使用 synchronized 時,- 等待的執行緒會一直等待下去,不能夠響應中斷;

  • 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。

  • Lock 可以提高多個執行緒進行讀操作的效率。

更深的:

  • 與 synchronized 相比,ReentrantLock 提供了更多,更加全面的功能,具備更強的擴充套件性。例如:時間鎖等候,可中斷鎖等候,鎖投票。

  • ReentrantLock 還提供了條件 Condition ,對執行緒的等待、喚醒操作更加詳細和靈活,所以在多個條件變數和高度競爭鎖的地方,ReentrantLock 更加適合(以後會闡述 Condition)。

  • ReentrantLock 提供了可輪詢的鎖請求。它會嘗試著去獲取鎖,如果成功則繼續,否則可以等到下次執行時處理,而 synchronized則一旦進入鎖請求要麼成功要麼阻塞,所以相比synchronized 而言,ReentrantLock 會不容易產生死鎖些。

  • ReentrantLock 支援更加靈活的同步程式碼塊,但是使用 synchronized時,只能在同一個synchronized塊結構中獲取和釋放。注意,ReentrantLock 的鎖釋放一定要在finally 中處理,否則可能會產生嚴重的後果。

  • ReentrantLock 支援中斷處理,且效能較 synchronized 會好些。

11、Java 中執行緒同步的方式

  • sychronized 同步方法或程式碼塊

  • volatile

  • Lock

  • ThreadLocal

  • 阻塞佇列(LinkedBlockingQueue)

  • 使用原子變數(java.util.concurrent.atomic)

  • 變數的不可變性

相關文章參考

Java併發程式設計:同步容器

java實現同步的幾種方式(總結)

12、CAS 是一種什麼樣的同步機制?多執行緒下為什麼不使用 int 而使用 AtomicInteger?

Compare And Swap,比較交換。可以看到 synchronized 可以保證程式碼塊原子性,很多時候會引起效能問題,volatile也是個不錯的選擇,但是volatile 不能保證原子性,只能在某些場合下使用。所以可以通過 CAS 來進行同步,保證原子性。

我們在讀 Concurrent 包下的類的原始碼時,發現無論是 ReentrantLock 內部的 AQS,還是各種 Atomic 開頭的原子類,內部都應用到了 CAS。

在 CAS 中有三個引數:記憶體值 V、舊的預期值 A、要更新的值 B ,當且僅當記憶體值 V 的值等於舊的預期值 A 時,才會將記憶體值 V 的值修改為 B,否則什麼都不幹。其虛擬碼如下:

if(this.value==A){
this.value=B
returntrue;
}else{
returnfalse;
}

CAS 可以保證一次的讀-改-寫操作是原子操作。

在多執行緒環境下,int 型別的自增操作不是原子的,執行緒不安全,可以使用 AtomicInteger 代替。

//AtomicInteger.java
privatestaticfinalUnsafeunsafe=Unsafe.getUnsafe();
privatestaticfinallongvalueOffset;
static{
try{
valueOffset=unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
}catch(Exceptionex){thrownewError(ex);}
}
privatevolatileintvalue;
  • Unsafe 是 CAS 的核心類,Java 無法直接訪問底層作業系統,而是通過本地 native` 方法來訪問。不過儘管如此,JVM 還是開了一個後門:Unsafe ,它提供了硬體級別的原子操作。

  • valueOffset 為變數值在記憶體中的偏移地址,Unsafe 就是通過偏移地址來得到資料的原值的。

  • value當前值,使用volatile 修飾,保證多執行緒環境下看見的是同一個。

//AtomicInteger.java
publicfinalintaddAndGet(intdelta){
returnunsafe.getAndAddInt(this,valueOffset,delta)+delta;
}

//Unsafe.java
//compareAndSwapInt(var1,var2,var5,var5+var4)其實換成compareAndSwapInt(obj,offset,expect,update)比較清楚,意思就是如果obj內的value和expect相等,就證明沒有其他執行緒改變過這個變數,那麼就更新它為update,如果這一步的CAS沒有成功,那就採用自旋的方式繼續進行CAS操作,取出乍一看這也是兩個步驟了啊,其實在JNI裡是藉助於一個CPU指令完成的。所以還是原子操作。
publicfinalintgetAndAddInt(Objectvar1,longvar2,intvar4){
intvar5;
do{
var5=this.getIntVolatile(var1,var2);
}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));
returnvar5;
}
//該方法為本地方法,有四個引數,分別代表:物件、物件的地址、預期值、修改值
publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);

13、HashMap 是不是執行緒安全?如何體現?如何變得安全?

由於新增元素到 map 中去時,資料量大產生擴容操作,多執行緒會導致 HashMap 的 node 連結串列形成環狀的資料結構產生死迴圈。所以 HashMap 是執行緒不安全的。

如何變得安全:
  • Hashtable:通過 synchronized 來保證執行緒安全的,獨佔鎖,悲觀策略。吞吐量較低,效能較為低下

  • SynchronizedHashMap :通過 Collections.synchronizedMap() 方法對 HashMap 進行包裝,返回一個 SynchronizedHashMap 物件,在原始碼中 SynchronizedHashMap 也是用過 synchronized 來保證執行緒安全的。但是實現方式和 Hashtable 略有不同(前者是 synchronized 方法,後者是通過 synchronized 對互斥變數加鎖實現)

  • ConcurrentHashMap:JUC 中的執行緒安全容器,高效併發。ConcurrentHashMap 的 key、value 都不允許為 null。

相關文章參考

HashMap實現原理淺析

Java中HashMap底層資料結構

集合系列—HashMap原始碼分析

14、ConcurrentHashMap 的實現方式?

ConcurrentHashMap 的實現方式和 Hashtable 不同,不採用獨佔鎖的形式,更高效,其中在 jdk1.7 和 jdk1.8 中實現的方式也略有不同。

Jdk1.7 中採用分段鎖和 HashEntry 使鎖更加細化。ConcurrentHashMap 採用了分段鎖技術,其中 Segment 繼承於 ReentrantLock。不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 支援 CurrencyLevel (Segment 陣列數量)的執行緒併發。

Jdk1.8 利用 CAS+Synchronized 來保證併發更新的安全,當然底層採用陣列+連結串列+紅黑樹的儲存結構。

  • table 中存放 Node 節點資料,預設 Node 資料大小為 16,擴容大小總是 2^N。

  • 為了保證可見性,Node 節點中的 val 和 next 節點都用 volatile 修飾。

  • 當連結串列長度大於 8 時,會轉換成紅黑樹,節點會被包裝成 TreeNode放在TreeBin 中。

  • put():1. 計算鍵所對應的 hash 值;2. 如果雜湊表還未初始化,呼叫 initTable() 初始化,否則在 table 中找到 index 位置,並通過 CAS 新增節點。如果連結串列節點數目超過 8,則將連結串列轉換為紅黑樹。如果節點總數超過,則進行擴容操作。

  • get():無需加鎖,直接根據 key 的 hash 值遍歷 node。

相關文章參考

Java併發系列 | ConcurrentHashMap原始碼分析

15、CountDownLatch 和 CyclicBarrier 的區別? 併發工具類

CyclicBarrier 它允許一組執行緒互相等待,直到到達某個公共屏障點 (Common Barrier Point)。在涉及一組固定大小的執行緒的程式中,這些執行緒必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 Barrier 在釋放等待執行緒後可以重用,所以稱它為迴圈 ( Cyclic ) 的 屏障 ( Barrier ) 。

每個執行緒呼叫 #await() 方法,告訴 CyclicBarrier 我已經到達了屏障,然後當前執行緒被阻塞。當所有執行緒都到達了屏障,結束阻塞,所有執行緒可繼續執行後續邏輯。

CountDownLatch 能夠使一個執行緒在等待另外一些執行緒完成各自工作之後,再繼續執行。使用一個計數器進行實現。計數器初始值為執行緒的數量。當每一個執行緒完成自己任務後,計數器的值就會減一。當計數器的值為 0 時,表示所有的執行緒都已經完成了任務,然後在 CountDownLatch 上等待的執行緒就可以恢復執行任務。

兩者區別:
  • CountDownLatch 的作用是允許 1 或 N 個執行緒等待其他執行緒完成執行;而 CyclicBarrier 則是允許 N 個執行緒相互等待。

  • CountDownLatch 的計數器無法被重置;CyclicBarrier 的計數器可以被重置後使用,因此它被稱為是迴圈的 barrier 。

Semaphore 是一個控制訪問多個共享資源的計數器,和 CountDownLatch 一樣,其本質上是一個“共享鎖”。一個計數訊號量。從概念上講,訊號量維護了一個許可集。

  • 如有必要,在許可可用前會阻塞每一個 acquire,然後再獲取該許可。

  • 每個 release 新增一個許可,從而可能釋放一個正在阻塞的獲取者。

相關文章參考

Java併發系列 | CountDownLatch原始碼分析

16、怎麼控制執行緒,儘可能減少上下文切換?

減少上下文切換的方法有無鎖併發程式設計、CAS演算法、使用最少執行緒和使用協程。

  • 無鎖併發程式設計。多執行緒競爭鎖時,會引起上下文切換,所以多執行緒處理資料時,可以使用一些方法來避免使用鎖。如將資料的ID按照Hash演算法取模分段,不同的執行緒處理不同段的資料。

  • CAS演算法。Java的Atomic包使用CAS演算法來更新資料,而不需要加鎖。

  • 使用最少執行緒。避免建立不需要的執行緒,比如任務很少,但是建立了很多執行緒來處理,這樣會造成大量執行緒都處於等待狀態。

  • 協程。在單執行緒裡實現多工的排程,並在單執行緒裡維持多個任務間的切換。

17、什麼是樂觀鎖和悲觀鎖?

像 synchronized這種獨佔鎖屬於悲觀鎖,它是在假設一定會發生衝突的,那麼加鎖恰好有用,除此之外,還有樂觀鎖,樂觀鎖的含義就是假設沒有發生衝突,那麼我正好可以進行某項操作,如果要是發生衝突呢,那我就重試直到成功,樂觀鎖最常見的就是CAS。

18、阻塞佇列

阻塞佇列實現了 BlockingQueue 介面,並且有多組處理方法。

丟擲異常:add(e) 、remove()、element()
返回特殊值:offer(e) 、pool()、peek()
阻塞:put(e) 、take()

JDK 8 中提供了七個阻塞佇列可供使用:

  • ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。

  • LinkedBlockingQueue :一個由連結串列結構組成的無界阻塞佇列。

  • PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。

  • DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。

  • SynchronousQueue:一個不儲存元素的阻塞佇列。

  • LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。

  • LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。

ArrayBlockingQueue,一個由陣列實現的有界阻塞佇列。該佇列採用 FIFO 的原則對元素進行排序新增的。內部使用可重入鎖 ReentrantLock + Condition 來完成多執行緒環境的併發操作。

相關文章參考

阻塞佇列 BlockingQueue

Java併發程式設計:阻塞佇列

19、執行緒池

執行緒池有五種狀態:RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED。

  • RUNNING:接收並處理任務。

  • SHUTDOWN:不接收但處理現有任務。

  • STOP:不接收也不處理任務,同時終端當前處理的任務。

  • TIDYING:所有任務終止,執行緒池會變為 TIDYING 狀態。當執行緒池變為 TIDYING 狀態時,會執行鉤子函式 terminated()。

  • TERMINATED:執行緒池徹底終止的狀態。

內部變數** ctl **定義為 AtomicInteger ,記錄了“執行緒池中的任務數量”和“執行緒池的狀態”兩個資訊。共 32 位,其中高 3 位表示”執行緒池狀態”,低 29 位表示”執行緒池中的任務數量”。

執行緒池建立引數

corePoolSize

執行緒池中核心執行緒的數量。當提交一個任務時,執行緒池會新建一個執行緒來執行任務,直到當前執行緒數等於 corePoolSize。如果呼叫了執行緒池的 prestartAllCoreThreads() 方法,執行緒池會提前建立並啟動所有基本執行緒。

maximumPoolSize

執行緒池中允許的最大執行緒數。執行緒池的阻塞佇列滿了之後,如果還有任務提交,如果當前的執行緒數小於 maximumPoolSize,則會新建執行緒來執行任務。注意,如果使用的是無界佇列,該引數也就沒有什麼效果了。

keepAliveTime

執行緒空閒的時間。執行緒的建立和銷燬是需要代價的。執行緒執行完任務後不會立即銷燬,而是繼續存活一段時間:keepAliveTime。預設情況下,該引數只有在執行緒數大於 corePoolSize 時才會生效。

unit

keepAliveTime 的單位。TimeUnit

workQueue

用來儲存等待執行的任務的阻塞佇列,等待的任務必須實現 Runnable 介面。我們可以選擇如下幾種:

  • ArrayBlockingQueue:基於陣列結構的有界阻塞佇列,FIFO。

  • LinkedBlockingQueue:基於連結串列結構的有界阻塞佇列,FIFO。

  • SynchronousQueue:不儲存元素的阻塞佇列,每個插入操作都必須等待一個移出操作,反之亦然。

  • PriorityBlockingQueue:具有優先界別的阻塞佇列。

threadFactory

用於設定建立執行緒的工廠。該物件可以通過 Executors.defaultThreadFactory()。他是通過 newThread() 方法提供建立執行緒的功能,newThread() 方法建立的執行緒都是“非守護執行緒”而且“執行緒優先順序都是 Thread.NORM_PRIORITY”。

handler

RejectedExecutionHandler,執行緒池的拒絕策略。所謂拒絕策略,是指將任務新增到執行緒池中時,執行緒池拒絕該任務所採取的相應策略。當向執行緒池中提交任務時,如果此時執行緒池中的執行緒已經飽和了,而且阻塞佇列也已經滿了,則執行緒池會選擇一種拒絕策略來處理該任務。

執行緒池提供了四種拒絕策略:
  • AbortPolicy:直接丟擲異常,預設策略;

  • CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;

  • DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務;

  • DiscardPolicy:直接丟棄任務;

當然我們也可以實現自己的拒絕策略,例如記錄日誌等等,實現 RejectedExecutionHandler 介面即可。

當新增新的任務到執行緒池時:
  • 執行緒數量未達到 corePoolSize,則新建一個執行緒(核心執行緒)執行任務

  • 執行緒數量達到了 corePoolSize,則將任務移入佇列等待

  • 佇列已滿,新建執行緒(非核心執行緒)執行任務

  • 佇列已滿,匯流排程數又達到了 maximumPoolSize,就會由 handler 的拒絕策略來處理

執行緒池可通過 Executor 框架來進行建立:
FixedThreadPool
publicstaticExecutorServicenewFixedThreadPool(intnThreads){
returnnewThreadPoolExecutor(nThreads,nThreads,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>());
}

corePoolSize 和 maximumPoolSize 都設定為建立 FixedThreadPool 時指定的引數 nThreads,意味著當執行緒池滿時且阻塞佇列也已經滿時,如果繼續提交任務,則會直接走拒絕策略,該執行緒池不會再新建執行緒來執行任務,而是直接走拒絕策略。FixedThreadPool 使用的是預設的拒絕策略,即 AbortPolicy,則直接丟擲異常。

但是 workQueue 使用了無界的 LinkedBlockingQueue, 那麼當任務數量超過 corePoolSize 後,全都會新增到佇列中而不執行拒絕策略。

SingleThreadExecutor
publicstaticExecutorServicenewSingleThreadExecutor(){
returnnewFinalizableDelegatedExecutorService
(newThreadPoolExecutor(1,1,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>()));
}

作為單一 worker 執行緒的執行緒池,SingleThreadExecutor 把 corePool 和 maximumPoolSize 均被設定為 1,和 FixedThreadPool 一樣使用的是無界佇列 LinkedBlockingQueue, 所以帶來的影響和 FixedThreadPool 一樣。

CachedThreadPool

CachedThreadPool是一個會根據需要建立新執行緒的執行緒池 ,他定義如下:

publicstaticExecutorServicenewCachedThreadPool(){
returnnewThreadPoolExecutor(0,Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,
newSynchronousQueue<Runnable>());
}

這個執行緒池,當任務提交是就會建立執行緒去執行,執行完成後執行緒會空閒60s,之後就會銷燬。但是如果主執行緒提交任務的速度遠遠大於 CachedThreadPool 的處理速度,則 CachedThreadPool 會不斷地建立新執行緒來執行任務,這樣有可能會導致系統耗盡 CPU 和記憶體資源,所以在使用該執行緒池是,一定要注意控制併發的任務數,否則建立大量的執行緒可能導致嚴重的效能問題。

相關文章參考

JAVA執行緒池原理詳解(1)

JAVA執行緒池原理詳解(2)

Java多執行緒和執行緒池

Java執行緒池總結

20、為什麼要使用執行緒池?

  • 建立/銷燬執行緒伴隨著系統開銷,過於頻繁的建立/銷燬執行緒,會很大程度上影響處理效率。執行緒池快取執行緒,可用已有的閒置執行緒來執行新任務(keepAliveTime)

  • 執行緒併發數量過多,搶佔系統資源從而導致阻塞。運用執行緒池能有效的控制執行緒最大併發數,避免以上的問題。

  • 對執行緒進行一些簡單的管理(延時執行、定時迴圈執行的策略等)

21、生產者消費者問題

例項程式碼用 Object 的 wait()和notify() 實現,也可用 ReentrantLock 和 Condition 來完成。或者直接使用阻塞佇列。

publicclassProducerConsumer{
publicstaticvoidmain(String[]args){
ProducerConsumermain=newProducerConsumer();
Queue<Integer>buffer=newLinkedList<>();
intmaxSize=5;
newThread(main.newProducer(buffer,maxSize),"Producer1").start();
newThread(main.newConsumer(buffer,maxSize),"Comsumer1").start();
newThread(main.newConsumer(buffer,maxSize),"Comsumer2").start();
}

classProducerimplementsRunnable{
privateQueue<Integer>queue;
privateintmaxSize;

Producer(Queue<Integer>queue,intmaxSize){
this.queue=queue;
this.maxSize=maxSize;
}

@Override
publicvoidrun(){
while(true){
synchronized(queue){
while(queue.size()==maxSize){
try{
System.out.println("Queueisfull");
queue.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
Randomrandom=newRandom();
inti=random.nextInt();
System.out.println(Thread.currentThread().getName()+"Producingvalue:"+i);
queue.add(i);
queue.notifyAll();
}
}
}
}

classConsumerimplementsRunnable{
privateQueue<Integer>queue;
privateintmaxSize;

publicConsumer(Queue<Integer>queue,intmaxSize){
super();
this.queue=queue;
this.maxSize=maxSize;
}

@Override
publicvoidrun(){
while(true){
synchronized(queue){
while(queue.isEmpty()){
try{
System.out.println("Queueisempty");
queue.wait();
}catch(Exceptionex){
ex.printStackTrace();
}
}
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"Consumingvalue:"+queue.remove());
queue.notifyAll();
}
}
}
}
}

相關文章參考

Java併發系列 | ConcurrentHashMap原始碼分析

Java併發系列 | CyclicBarrier原始碼分析

Java併發系列 | CountDownLatch原始碼分析

Java併發系列 | Semaphore原始碼分析

Java併發系列 | ReentrantLock原始碼分析

Java併發系列 | AbstractQueuedSynchronizer原始碼分析之條件佇列

Java併發系列 | AbstractQueuedSynchronizer原始碼分析之共享模式

Java併發系列 | AbstractQueuedSynchronizer原始碼分析之概要分析

Java併發系列 | AbstractQueuedSynchronizer原始碼分析之獨佔模式

來源:www.cnblogs.com/Sinte-Beuve