突擊併發編程JUC系列-萬字長文解密 JUC 面試題

突擊併發編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial

什麼是 CAS 嗎?

CAS(Compare And Swap)指比較並交換。CAS算法CAS(V, E, N)包含 3 個參數,V 表示要更新的變量,E 表示預期的值,N 表示新值。在且僅在 V 值等於 E值時,纔會將 V 值設爲 N,若是 V 值和 E 值不一樣,則說明已經有其餘線程作了更新,當前線程什麼都不作。最後,CAS 返回當前 V 的真實值。Concurrent包下全部類底層都是依靠CAS操做來實現,而sun.misc.Unsafe爲咱們提供了一系列的CAS操做。前端

CAS 有什麼缺點?

  • ABA問題
  • 自旋問題
  • 範圍不能靈活控制

對 CAS 中的 ABA 產生有解決方案嗎?

什麼是 ABA 問題呢?多線程環境下。線程 1 從內存的V位置取出 A ,線程 2 也從內存中取出 A,並將 V 位置的數據首先修改成 B,接着又將 V 位置的數據修改成 A,線程 1 在進行CAS操做時會發如今內存中仍然是 A,線程 1 操做成功。儘管從線程 1 的角度來講,CAS操做是成功的,但在該過程當中其實 V 位置的數據發生了變化,線程 1 沒有感知到罷了,這在某些應用場景下可能出現過程數據不一致的問題。java

能夠版本號(version)來解決 ABA 問題的,在 atomic 包中提供了 AtomicStampedReference 這個類,它是專門用來解決 ABA 問題的。git

直達連接: AtomicStampedReference ABA 案例連接

CAS 自旋致使的問題?

因爲單次 CAS 不必定能執行成功,因此 CAS 每每是配合着循環來實現的,有的時候甚至是死循環,不停地進行重試,直到線程競爭不激烈的時候,才能修改爲功。程序員

CPU 資源也是一直在被消耗的,這會對性能產生很大的影響。因此這就要求咱們,要根據實際狀況來選擇是否使用 CAS,在高併發的場景下,一般 CAS 的效率是不高的。github

CAS 範圍不能靈活控制

不能靈活控制線程安全的範圍。只能針對某一個,而不是多個共享變量的,不能針對多個共享變量同時進行 CAS 操做,由於這多個變量之間是獨立的,簡單的把原子操做組合到一塊兒,並不具有原子性。算法

什麼是 AQS 嗎?

AbstractQueuedSynchronizer抽象同步隊列簡稱AQS,它是實現同步器的基礎組件,併發包中鎖的底層就是使用AQS實現的。AQS定義了一套多線程訪問共享資源的同步框架,許多同步類的實現都依賴於它,例如經常使用的SynchronizedReentrantLockReentrantReadWriteLockSemaphoreCountDownLatch等。該框架下的鎖會先嚐試以CAS樂觀鎖去獲取鎖,若是獲取不到,則會轉爲悲觀鎖(如RetreenLock)。編程

瞭解 AQS 共享資源的方式嗎?

  • 獨佔式:只有一個線程能執行,具體的Java實現有ReentrantLock
  • 共享式:多個線程可同時執行,具體的Java實現有SemaphoreCountDownLatch

Atomic 原子更新

JavaJDK1.5 開始提供了 java.util.concurrent.atomic 包,方便程序員在多線程環 境下,無鎖的進行原子操做。在 Atomic 包裏一共有 12 個類,四種原子更新方式,分別是原子更新基本類型原子更新數組原子更新引用原子更新字段。在 JDK 1.8 以後又新增幾個原子類。以下如:
image後端

針對思惟導圖知識點在前面的章節都進行了理論+實踐的講解,到達地址以下:數組

突擊併發編程JUC系列-原子更新AtomicLong<br/>
突擊併發編程JUC系列-數組類型AtomicLongArray<br/>
突擊併發編程JUC系列-原子更新字段類AtomicStampedReference<br/>
突擊併發編程JUC系列-JDK1.8 擴展類型 LongAdder

列舉幾個AtomicLong 的經常使用方法

  • long getAndIncrement() :以原子方式將當前值加1,注意,返回的是舊值。(i++)
  • long incrementAndGet() :以原子方式將當前值加1,注意,返回的是新值。(++i)
  • long getAndDecrement() :以原子方式將當前值減 1,注意,返回的是舊值 。(i--)
  • long decrementAndGet() :以原子方式將當前值減 1,注意,返回的是新值 。(--i)
  • long addAndGet(int delta) :以原子方式將輸入的數值與實例中的值(AtomicLong裏的value)相加,並返回結果

說說 AtomicInteger 和 synchronized 的異同點?

相同點

  • 都是線程安全

不一樣點

  • 一、背後原理
    synchronized 背後的 monitor 鎖。在執行同步代碼以前,須要首先獲取到 monitor 鎖,執行完畢後,再釋放鎖原子類,線程安全的原理是利用了 CAS 操做
  • 二、使用範圍
    原子類使用範圍是比較侷限的,一個原子類僅僅是一個對象,不夠靈活。而 synchronized 的使用範圍要普遍得多。好比說 synchronized 既能夠修飾一個方法,又能夠修飾一段代碼,至關於能夠根據咱們的須要,很是靈活地去控制它的應用範圍
  • 三、粒度
    原子變量的粒度是比較小的,它能夠把競爭範圍縮小到變量級別。一般狀況下,synchronized 鎖的粒度都要大於原子變量的粒度
  • 四、性能
    synchronized 是一種典型的悲觀鎖,而原子類偏偏相反,它利用的是樂觀鎖。

原子類和 volatile 有什麼異同?

  • volatile 可見性問題
  • 解決原子性問題

AtomicLong 能否被 LongAdder 替代?

有了更高效的 LongAdder,那 AtomicLong 能否不使用了呢?是否凡是用到 AtomicLong 的地方,均可以用 LongAdder 替換掉呢?答案是否是的,這須要區分場景。緩存

LongAdder 只提供了 addincrement 等簡單的方法,適合的是統計求和計數的場景,場景比較單一,而 AtomicLong 還具備 compareAndSet 等高級方法,能夠應對除了加減以外的更復雜的須要 CAS 的場景。

結論:若是咱們的場景僅僅是須要用到加和減操做的話,那麼能夠直接使用更高效的 LongAdder,但若是咱們須要利用 CAS 好比compareAndSet 等操做的話,就須要使用 AtomicLong 來完成。

直達連接: 突擊併發編程JUC系列-JDK1.8 擴展類型 LongAdder

併發工具

CountDownLatch

CountDownLatch基於線程計數器來實現併發訪問控制,主要用於主線程等待其餘子線程都執行完畢後執行相關操做。其使用過程爲:在主線程中定義CountDownLatch,並將線程計數器的初始值設置爲子線程的個數,多個子線程併發執行,每一個子線程在執行完畢後都會調用countDown函數將計數器的值減1,直到線程計數器爲0,表示全部的子線程任務都已執行完畢,此時在CountDownLatch上等待的主線程將被喚醒並繼續執行。

突擊併發編程JUC系列-併發工具 CountDownLatch

CyclicBarrier

CyclicBarrier(循環屏障)是一個同步工具,能夠實現讓一組線程等待至某個狀態以後再所有同時執行。在全部等待線程都被釋放以後,CyclicBarrier能夠被重用。CyclicBarrier的運行狀態叫做Barrier狀態,在調用await方法後,線程就處於Barrier狀態。

CyclicBarrier中最重要的方法是await方法,它有兩種實現。

  • public int await():掛起當前線程直到全部線程都爲Barrier狀態再同時執行後續的任務。
  • public int await(long timeout, TimeUnit unit):設置一個超時時間,在超時時間事後,若是還有線程未達到Barrier狀態,則再也不等待,讓達到Barrier狀態的線程繼續執行後續的任務。
突擊併發編程JUC系列-併發工具 CyclicBarrier

Semaphore

Semaphore指信號量,用於控制同時訪問某些資源的線程個數,具體作法爲經過調用acquire()獲取一個許可,若是沒有許可,則等待,在許可以使用完畢後經過release()釋放該許可,以便其餘線程使用。

突擊併發編程JUC系列-併發工具 Semaphore

CyclicBarrier 和 CountdownLatch 有什麼異同?

相同點:都能阻塞一個或一組線程,直到某個預設的條件達成發生,再統一出發。
可是它們也有不少不一樣點,具體以下。

  • 做用對象不一樣:CyclicBarrier 要等固定數量的線程都到達了柵欄位置才能繼續執行,而 CountDownLatch 只需等待數字倒數到 0,也就是說 CountDownLatch 做用於事件,但 CyclicBarrier 做用於線程;CountDownLatch 是在調用了 countDown 方法以後把數字倒數減 1,而 CyclicBarrier 是在某線程開始等待後把計數減 1。
  • 可重用性不一樣:CountDownLatch 在倒數到 0 而且觸發門閂打開後,就不能再次使用了,除非新建一個新的實例;而 CyclicBarrier 能夠重複使用CyclicBarrier 還能夠隨時調用 reset 方法進行重置,若是重置時有線程已經調用了 await 方法並開始等待,那麼這些線程則會拋出 BrokenBarrierException 異常。
  • 執行動做不一樣:CyclicBarrier 有執行動做 barrierAction,而 CountDownLatch 沒這個功能。

CountDownLatch、CyclicBarrier、Semaphore的區別以下。

  • CountDownLatchCyclicBarrier都用於實現多線程之間的相互等待,但兩者的關注點不一樣。CountDownLatch主要用於主線程等待其餘子線程任務均執行完畢後再執行接下來的業務邏輯單元,而CyclicBarrier主要用於一組線程互相等待你們都達到某個狀態後,再同時執行接下來的業務邏輯單元。此外,CountDownLatch是不能夠重用的,而CyclicBarrier是能夠重用的。
  • SemaphoreJava中的鎖功能相似,主要用於控制資源的併發訪問。

locks

公平鎖與非公平鎖

ReentrantLock支持公平鎖和非公平鎖兩種方式。公平鎖指鎖的分配和競爭機制是公平的,即遵循先到先得原則。非公平鎖指JVM遵循隨機、就近原則分配鎖的機制。ReentrantLock經過在構造函數ReentrantLock(boolean fair)中傳遞不一樣的參數來定義不一樣類型的鎖,默認的實現是非公平鎖。這是由於,非公平鎖雖然放棄了鎖的公平性,可是執行效率明顯高於公平鎖。若是系統沒有特殊的要求,通常狀況下建議使用非公平鎖。

synchronized 和 lock 有什麼區別?

  • synchronized 能夠給類,方法,代碼塊加鎖,而 lock 只能給代碼塊加鎖。
  • synchronized 不須要手動獲取鎖和釋放鎖,使用簡單,發生異常會自動釋放鎖,不會形成死鎖,而 lock 須要手動本身加鎖和釋放鎖,若是使用不當沒有 unLock 去釋放鎖,就會形成死鎖。
  • 經過 lock 能夠知道有沒有成功獲取鎖,而 synchronized 沒法辦到。

synchronized 和 Lock 如何選擇?

  • synchronized Lock 都是用來保護資源線程安全的。
  • 都保證了可見性和互斥性。
  • synchronizedReentrantLock 都擁有可重入的特色。

不一樣點:

  • 用法(lock 須要配合finally
  • ReentrantLock可響應中斷、可輪迴,爲處理鎖提供了更多的靈活性
  • ReentrantLock經過Condition能夠綁定多個條件
  • 加解鎖順序()
  • synchronized 鎖不夠靈活
  • 是否能夠設置公平/非公平
  • 兩者的底層實現不同:synchronized是同步阻塞,採用的是悲觀併發策略;Lock是同步非阻塞,採用的是樂觀併發策略。

使用

  • 若是能不用最好既不使用 Lock 也不使用 synchronized
  • 若是 synchronized 關鍵字適合你的程序,這樣能夠減小編寫代碼的數量,減小出錯的機率
  • 若是特別須要 Lock 的特殊功能,好比嘗試獲取鎖、可中斷、超時功能等,才使用 Lock

Lock接口的主要方法

  • void lock():獲取鎖,調用該方法當前線程將會獲取鎖,當鎖得到後,從該方法返回
  • void lockInterruptibly() throws InterruptedException:可中斷地獲取鎖,和lock方法地不一樣之處在於該方法會響應中斷,即在鎖的獲取中能夠中斷當前線程
  • boolean tryLock(): 嘗試非阻塞地獲取鎖,調用該方法後馬上返回,若是可以獲取則返回 true 不然 返回false
  • boolean tryLock(long time, TimeUnit unit):超時地獲取鎖,當前線程在如下 3 種狀況下會返回:
    • 當前線程在超時時間內得到了鎖
    • 當前線程在超時時間被中斷
    • 超時時間結束後,返回 false
  • void unlock(): 釋放鎖
  • Condition newCondition():獲取鎖等待通知組件,該組件和當前的鎖綁定,當前線程只有得到了鎖,才能調用該組件的 wait() 方法,而調用後,當前線程將釋放鎖。

tryLock、lock和lockInterruptibly的區別

tryLocklocklockInterruptibly的區別以下。

  • tryLock如有可用鎖,則獲取該鎖並返回true,不然返回false,不會有延遲或等待;tryLock(long timeout, TimeUnit unit)能夠增長時間限制,若是超過了指定的時間還沒得到鎖,則返回 false。
  • lock如有可用鎖,則獲取該鎖並返回true,不然會一直等待直到獲取可用鎖。
  • 在鎖中斷時lockInterruptibly會拋出異常,lock不會。
突擊併發編程JUC系列-ReentrantLock

ReentrantReadWriteLock 讀寫鎖的獲取規則

要麼是一個或多個線程同時有讀鎖,要麼是一個線程有寫鎖,可是二者不會同時出現。也能夠總結爲:讀讀共享、其餘都互斥(寫寫互斥、讀寫互斥、寫讀互斥)

ReentrantLock 適用於通常場合,ReadWriteLock 適用於讀多寫少的狀況,合理使用能夠進一步提升併發效率。

突擊併發編程JUC系列-ReentrantReadWriteLock

讀鎖應該插隊嗎?什麼是讀寫鎖的升降級?

ReentrantReadWriteLock 的實現選擇了「不容許插隊」的策略,這就大大減少了發生「飢餓」的機率。

插隊策略

  • 公平策略下,只要隊列裏有線程已經在排隊,就不容許插隊。
  • 非公平策略下:
    • 若是容許讀鎖插隊,那麼因爲讀鎖能夠同時被多個線程持有,因此可能形成源源不斷的後面的線程一直插隊成功,致使讀鎖一直不能徹底釋放,從而致使寫鎖一直等待,爲了防止「飢餓」,在等待隊列的頭結點是嘗試獲取寫鎖的線程的時候,不容許讀鎖插隊。
    • 寫鎖能夠隨時插隊,由於寫鎖並不容易插隊成功,寫鎖只有在當前沒有任何其餘線程持有讀鎖和寫鎖的時候,才能插隊成功,同時寫鎖一旦插隊失敗就會進入等待隊列,因此很難形成「飢餓」的狀況,容許寫鎖插隊是爲了提升效率。

升降級策略:只能從寫鎖降級爲讀鎖,不能從讀鎖升級爲寫鎖。

怎麼防止死鎖?

  • 儘可能使用 tryLock(long timeout,TimeUnit unit) 的方法(ReentrantLock 、ReenttranReadWriteLock)設置超時時間,超時能夠退出防止死鎖。
  • 儘可能使用 java.util.concurrent 併發類代替手寫鎖。
  • 儘可能下降鎖的使用粒度,儘可能不要幾個功能用同一把鎖。
  • 儘可能減小同步的代碼塊。

Condition 類和 Object 類鎖方法區別區別

  • Condition 類的 awiat 方法和 Object 類的 wait 方法等效
  • Condition 類的 signal 方法和 Object 類的 notify 方法等效
  • Condition 類的 signalAll 方法和 Object 類的 notifyAll 方法等效
  • ReentrantLock 類能夠喚醒指定條件的線程,而 object 的喚醒是隨機的

併發容器

爲何 ConcurrentHashMap 比 HashTable 效率要高?

  • HashTable 使用一把鎖(鎖住整個鏈表結構)處理併發問題,多個線程競爭一把鎖,容易阻塞;
  • ConcurrentHashMap

    • JDK 1.7 中使用分段鎖(ReentrantLock + Segment + HashEntry),至關於把一個 HashMap 分紅多個段,每段分配一把鎖,這樣支持多線程訪問。鎖粒度:基於 Segment,包含多個 HashEntry
    • JDK 1.8 中使用 CAS + synchronized + Node + 紅黑樹。鎖粒度:Node(首結點)(實現 Map.Entry)。鎖粒度下降了。

ConcurrentHashMap JDK 1.7/JDK 1.8

JDK 1.7 結構

image

JDK 1.7 中的ConcurrentHashMap 內部進行了 Segment分段,Segment 繼承了 ReentrantLock,能夠理解爲一把鎖,各個 Segment 之間都是相互獨立上鎖的,互不影響。
相比於以前的 Hashtable 每次操做都須要把整個對象鎖住而言,大大提升了併發效率。由於它的鎖與鎖之間是獨立的,而不是整個對象只有一把鎖。
每一個 Segment 的底層數據結構與 HashMap 相似,仍然是數組和鏈表組成的拉鍊法結構。默認有 0~15 共 16 個 Segment,因此最多能夠同時支持 16 個線程併發操做(操做分別分佈在不一樣的 Segment 上)。16 這個默認值能夠在初始化的時候設置爲其餘值,可是一旦確認初始化之後,是不能夠擴容的。

JDK 1.8 結構

image

圖中的節點有三種類型: 

  • 第一種是最簡單的,空着的位置表明當前尚未元素來填充。
  • 第二種就是和 HashMap 很是相似的拉鍊法結構,在每個槽中會首先填入第一個節點,可是後續若是計算出相同的 Hash 值,就用鏈表的形式日後進行延伸。
  • 第三種結構就是紅黑樹結構,這是 Java 7 的 ConcurrentHashMap 中所沒有的結構,在此以前咱們可能也不多接觸這樣的數據結構

鏈表長度大於某一個閾值(默認爲 8),知足容量從鏈表的形式轉化爲紅黑樹的形式。
紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色爲紅色或黑色,紅黑樹的本質是對二叉查找樹 BST 的一種平衡策略,咱們能夠理解爲是一種平衡二叉查找樹,查找效率高,會自動平衡,防止極端不平衡從而影響查找效率的狀況發生,紅黑樹每一個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的。

ConcurrentHashMap 中 get 的過程

  • 計算 Hash 值,並由此值找到對應的槽點;
  • 若是數組是空的或者該位置爲 null,那麼直接返回 null 就能夠了;
  • 若是該位置處的節點恰好就是咱們須要的,直接返回該節點的值;
  • 若是該位置節點是紅黑樹或者正在擴容,就用 find 方法繼續查找;
  • 不然那就是鏈表,就進行遍歷鏈表查找

ConcurrentHashMap 中 put 的過程

  • 判斷 Node[] 數組是否初始化,沒有則進行初始化操做
  • 經過 hash 定位數組的索引座標,是否有 Node 節點,若是沒有則使用 CAS 進行添加(鏈表的頭節點),添加失敗則進入下次循環。
  • 檢查到內部正在擴容,就幫助它一塊擴容。
  • 若是 f != null ,則使用 synchronized 鎖住 f 元素(鏈表/紅黑二叉樹的頭元素)
    • 若是是 Node (鏈表結構)則執行鏈表的添加操做
    • 若是是 TreeNode (樹形結構)則執行樹添加操做。
  • 判斷鏈表長度已經達到臨界值 8 ,固然這個 8 是默認值,你們也能夠去作調整,當節點數超過這個值就須要把鏈表轉換爲樹結構。
突擊併發編程JUC系列-併發容器ConcurrentHashMap

什麼是阻塞隊列?

阻塞隊列(BlockingQueue)是一個支持兩個附加操做的隊列。這兩個附加的操做支持阻塞的插入和移除方法。

  • 支持阻塞的插入方法:意思是當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。
  • 支持阻塞的移除方法:意思是在隊列爲空時,獲取元素的線程會等待隊列變爲非空。

阻塞隊列經常使用於生產者和消費者的場景,生產者是向隊列裏添加元素的線程,消費者是從隊列裏取元素的線程。阻塞隊列就是生產者用來存放元素、消費者用來獲取元素的容器。

列舉幾個常見的阻塞隊列

  • ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
  • LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
  • PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
  • DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。
  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
突擊併發編程JUC系列-阻塞隊列 BlockingQueue

線程池

使用線程池的優點

Java 中的線程池是運用場景最多的併發框架,幾乎全部須要異步或併發執行任務的程序均可以使用線程池。

  • 下降資源消耗。 經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
  • 提升響應速度。 當任務到達時,任務能夠不須要等到線程建立就能當即執行。
  • 提升線程的可管理性。 線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控。可是,要作到合理利用線程池,必須對其實現原理了如指掌。

線程池的實現原理

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

  • 線程池判斷核心線程池裏的線程是否都在執行任務。若是不是,則建立一個新的工做線程來執行任務。若是核心線程池裏的線程都在執行任務,則進入下個流程。
  • 線程池判斷工做隊列是否已經滿。若是工做隊列沒有滿,則將新提交的任務存儲在這個工做隊列裏。若是工做隊列滿了,則進入下個流程。
  • 線程池判斷線程池的線程是否都處於工做狀態。若是沒有,則建立一個新的工做線程來執行任務。若是已經滿了,則交給飽和策略來處理這個任務。

image

ThreadPoolExecutor執行execute()方法的示意圖 以下:
image

ThreadPoolExecutor執行execute方法分下面 4 種狀況:

  • 一、若是當前運行的線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟須要獲取全局鎖)。
  • 二、若是運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue
  • 三、若是沒法將任務加入BlockingQueue(隊列已滿),則建立新的線程來處理任務(注意,執行這一步驟須要獲取全局鎖)。
  • 四、若是建立新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor採起上述步驟的整體設計思路,是爲了在執行execute()方法時,儘量地避免獲取全局鎖(那將會是一個嚴重的可伸縮瓶頸)。在ThreadPoolExecutor完成預熱以後(當前運行的線程數大於等於corePoolSize),幾乎全部的execute()方法調用都是執行步驟 2,而步驟2不須要獲取全局鎖。

建立線程有三種方式:

  • 繼承 Thread 重寫 run 方法
  • 實現 Runnable 接口
  • 實現 Callable 接口 (有返回值)

線程有哪些狀態?

  • NEW(初始),新建狀態,線程被建立出來,但還沒有啓動時的線程狀態;
  • RUNNABLE(就緒狀態),表示能夠運行的線程狀態,它可能正在運行,或者是在排隊等待操做系統給它分配 CPU 資源;
  • BLOCKED(阻塞),阻塞等待鎖的線程狀態,表示處於阻塞狀態的線程正在等待監視器鎖,好比等待執行 synchronized 代碼塊或者使用 synchronized 標記的方法;
  • WAITING(等待),等待狀態,一個處於等待狀態的線程正在等待另外一個線程執行某個特定的動做,好比,一個線程調用了 Object.wait() 方法,那它就在等待另外一個線程調用 Object.notify()Object.notifyAll() 方法;
  • TIMED_WAITING(超時等待),計時等待狀態,和等待狀態(WAITING)相似,它只是多了超時時間,好比調用了有超時時間設置的方法 Object.wait(long timeout) Thread.join(long timeout) 等這些方法時,它纔會進入此狀態;
  • TERMINATED,終止狀態,表示線程已經執行完成。

image

線程池的狀態有那些?

  • running :這是最正常的狀態,接受新的任務,處理等待隊列中的任務。
  • shutdown:不接受新的任務提交,可是會繼續處理等待隊列中的任務。
  • stop:不接受新的任務提交,再也不處理等待隊列中的任務,中斷正在執行任務的線程。
  • tidying:全部的任務都銷燬了,workcount 爲 0,線程池的狀態再轉換 tidying 狀態時,會執行鉤子方法 terminated()
  • terminated terminated() 方法結束後,線程池的狀態就會變成這個。

image

線程池中 sumbit() 和 execute() 方法有什麼區別?

  • execute(): 只能執行 Runable 類型的任務。
  • submit() 能夠執行 Runable Callable 類型的任務。

Callable 類型的任務能夠獲取執行的返回值,而 Runnable 執行無返回值。

線程池建立的方式

  • newSingleThreadExecutor(): 他的特色是在於線程數目被限制位1:操做一個無界的工做隊列,因此它保證了全部的任務的都是順序執行,最多會有一個任務處於活動狀態,而且不容許使用者改動線程池實例,所以能夠避免其改變線程數目。
  • newCachedThreadPool():它是一種用來處理大量短期工做任務的線程,具備幾個鮮明的特色,它會試圖緩存線程並重用,當無緩存線程可用時,就會建立新的工做線程,若是線程閒置的時間超過 60 秒,則被終止並移除緩存;長時間閒置時,這種線程池不會消耗什麼資源,其內部使用 synchronousQueue 做爲工做隊列。
  • newFixedThreadPool(int nThreads) :重用指定數目 nThreads 的線程,其背後使用的無界的工做隊列,任什麼時候候最後有 nThreads 個工做線程活動的,這意味着 若是任務數量超過了活動隊列數目,將在工做隊列中等待空閒線程出現,若是有工做線程退出,將會有新的工做線程被建立,以補足指定的數目 nThreads。
  • newSingleThreadScheduledExecutor(): 建立單線程池,返回ScheduleExecutorService 能夠進行定時或週期性的工做強度。
  • newScheduleThreadPool(int corePoolSize): 和 newSingleThreadSceduleExecutor() 相似,建立的ScheduledExecutorService能夠進行定時或週期的工做調度,區別在於單一工做線程仍是工做線程。
  • newWorkStrealingPool(int parallelism):這是一個常常被人忽略的線程池,Java 8 才加入這個建立方法,其內部會構建ForkJoinPool利用 work-strealing 算法 並行的處理任務,不保證處理順序。
  • ThreadPollExecutor : 是最原始的線程池建立,上面 1-3 建立方式 都是對ThreadPoolExecutor 的封裝。

上面 7 種建立方式中,前 6 種 經過Executors工廠方法建立,ThreadPoolExecutor 手動建立。

ThreadPollExecutor 構造方法

下面介紹下 ThreadPoolExecutor 接收 7 個參數的構造方法

/**
     * 用給定的初始參數建立一個新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數量
                              int maximumPoolSize,//線程池的最大線程數
                              long keepAliveTime,//當線程數大於核心線程數時,多餘的空閒線程存活的最長時間
                              TimeUnit unit,//時間單位
                              BlockingQueue<Runnable> workQueue,//任務隊列
                              ThreadFactory threadFactory,//線程工廠
                              RejectedExecutionHandler handler//拒絕策略
                               )
  • corePoolSize : 核心線程數線程數定義了最小能夠同時運行的線程數量。
  • maximumPoolSize : 當隊列中存放的任務達到隊列容量的時候,當前能夠同時運行的線程數量變爲最大線程數。
  • workQueue: 當新任務來的時候會先判斷當前運行的線程數量是否達到核心線程數,若是達到的話,信任就會被存放在隊列中。
  • keepAliveTime:線程活動保持時間,當線程池中的線程數量大於 corePoolSize 的時候,若是這時沒有新的任務提交,核心線程外的線程不會當即銷燬,而是會等待,直到等待的時間超過了 keepAliveTime纔會被回收銷燬;
  • unit : keepAliveTime 參數的時間單位。
  • threadFactory : 任務隊列,用於保存等待執行的任務的阻塞隊列。能夠選擇如下幾個阻塞隊列。

    • ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
    • LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
    • SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
    • PriorityBlockingQueue:一個具備優先級的無限阻塞隊列。
  • handler :飽和策略(又稱拒絕策略)。當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採起一種策略處理提交的新任務。這個策略默認狀況下是AbortPolicy,表示沒法處理新任務時拋出異常。在JDK 1.5 中 Java 線程池框架提供瞭如下4種策略。

    • AbortPolicy:直接拋出異常。
    • CallerRunsPolicy:只用調用者所在線程來運行任務。
    • DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
    • DiscardPolicy:不處理,丟棄掉

歡迎關注公衆號 山間木匠 , 我是小春哥,從事 Java 後端開發,會一點前端、經過持續輸出系列技術文章以文會友,若是本文能爲您提供幫助,歡迎你們關注、 點贊、分享支持,_咱們下期再見!_

相關文章
相關標籤/搜索