Java 8併發工具包漫遊指南

Java 8併發工具包簡介

Java 8併發工具包由3個包組成,分別是java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.locks,提供了大量關於併發的接口、類、原子操做類、鎖相關類。藉助java.util.concurrent包,能夠很是輕鬆地實現複雜的併發操做。java.util.concurrent包主要包含如下內容,後文將具體介紹:java

阻塞隊列:多種阻塞隊列的實現,在隊列爲空或滿時可以阻塞消費者或生產者相關線程。算法

併發容器:用於併發場景的容器類型,通常無需加鎖。編程

線程池:建立單線程、固定大小或可緩存的線程池,支持週期性任務,也可以實現異步任務的返回。數組

鎖:用於併發同步的鎖;緩存

原子類型:用於實現原子操做的數據類型,包括AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等安全

併發工具:用於併發場景的類,能夠控制併發線程的同步、等待。併發

 

阻塞隊列BlockingQueue

在BlockingQueue中,生產者能夠持續向隊列插入新的元素,直到隊列滿爲止,隊列滿後生產者線程被阻塞;消費者能夠持續從隊列取出元素,直到隊列空爲止,隊列空後消費者線程被阻塞。dom

BlockingQueue提供了四種類型的操做方法,在操做不能當即執行的狀況下有不一樣的表現。異步

  拋出異常 返回特殊值 阻塞 超時返回
插入 add(o) offer(o) put(o) offer(o, timeout, timeunit)
刪除 remove(o) poll() take() poll(timeout, timeunit)
檢查 element() peek()    

Java 8提供了多種類型的BlockingQueue實現類,高併發

ArrayBlockingQueue:基於數組實現的有界阻塞隊列,建立後不能修改隊列的大小;

LinkedBlockingQueue:基於鏈表實現的有界阻塞隊列,默認大小爲Integer.MAX_VALUE,有較好的吞吐量,但可預測性差。

PriorityBlockingQueue:具備優先級的無界阻塞隊列,不容許插入null,全部元素都必須可比較(即實現Comparable接口)。

SynchronousQueue:只有一個元素的同步隊列。若隊列中有元素插入操做將被阻塞,直到隊列中的元素被其餘線程取走。

DelayQueue:無界阻塞隊列,每一個元素都有一個延遲時間,在延遲時間以後才釋放元素。

 

阻塞雙端隊列BlockingDueue

Dueue是「Double Ended Queue」的縮寫。生產者和消費者能夠在隊列的兩端進行插入和刪除操做。

在頭部提供了四種類型的操做方法,在操做不能當即執行的狀況下有不一樣的表現。

  拋出異常 返回特殊值 阻塞 超時返回
插入 addFirst(o) offerFirst(o) putFirst(o) offerFirst(o, timeout, timeunit)
刪除 removeFirst(o) pollFirst(o) takeFirst(o) pollFirst(timeout, timeunit)
檢查 getFirst(o) peekFirst(o)    

在尾部提供了四種類型的操做方法,在操做不能當即執行的狀況下有不一樣的表現。

  拋出異常 返回特殊值 阻塞 超時返回
插入 addLast(o) offerLast(o) putLast(o) offerLast(o, timeout, timeunit)
刪除 removeLast(o) pollLast(o) takeLast(o) pollLast(timeout, timeunit)
檢查 getLast(o) peekLast(o)    

Java 8只提供了一種類型的BlockingDueue實現類,

LinkedBlockingDeque:基於雙向鏈表實現的有界阻塞隊列,默認大小爲Integer.MAX_VALUE,有較好的吞吐量,但可預測性差。

 

阻塞轉移隊列TransferQueue

TransferQueue接口繼承了BlockingQueue接口,所以具備BlockingQueue接口的全部方法,並增長了一些方法。方法及做用以下:

tryTransfer(E e):若當前存在一個正在等待獲取的消費者線程,則該方法會即刻轉移e,並返回true;若不存在則返回false,可是並不會將e插入到隊列中。這個方法不會阻塞當前線程,要麼快速返回true,要麼快速返回false。

transfer(E e):若當前存在一個正在等待獲取的消費者線程,即馬上將e移交之;不然將元素e插入到隊列尾部,而且當前線程進入阻塞狀態,直到有消費者線程取走該元素。

tryTransfer(E e, long timeout, TimeUnit unit):若當前存在一個正在等待獲取的消費者線程,會當即傳輸給它; 不然將元素e插入到隊列尾部,而且等待被消費者線程獲取消費掉。若在指定的時間內元素e沒法被消費者線程獲取,則返回false,同時該元素從隊列中移除。

Java 8 提供了一個基於鏈表的實現類LinkedTransferQueue。

 

併發容器

工具包提供了隊列的併發實現類ConcurrentLinkedQueue和ConcurrentLinkedDeque,二者都是無界非阻塞線程安全的隊列。

ConcurrentMap接口繼承了普通的Map接口,提供了線程安全和原子操做特性。Java 8 提供了實現類ConcurrentHashMap,ConcurrentHashMap不鎖定整個Map,只鎖定須要寫入的部分,所以併發性能比HashTable要高不少。

ConcurrentNavigableMap接口繼承了ConcurrentMap和NavigableMap接口,支持併發訪問NavigableMap,還能讓子Map具有併發訪問的能力。NavigableMap是擴展的 SortedMap,具備了針對給定搜索目標返回最接近匹配項的導航方法。

Java 8 提供了實現類ConcurrentSkipListMap,並無使用lock來保證線程的併發訪問和修改,而是使用了非阻塞算法來保證併發訪問,高併發時相對於TreeMap有明顯的優點。

工具包提供了NavigableSet的併發實現類ConcurrentSkipListSet,是線程安全的有序集合,適用於高併發的場景,經過ConcurrentSkipListMap實現。

工具包提供了兩個寫時複製容器,即CopyOnWriteArrayList和CopyOnWriteArraySet。寫時複製技術是一種優化策略,多個線程能夠併發訪問同一份數據,當有線程要修改時才進行復制而後修改。在Linux系統中,fork進程後,子進程先與父進程共享數據,須要修改時才用寫時複製獲得本身的副本。在Java中,寫時複製容器在修改數據後,把原來容器的引用指向新容器,來實現讀寫分離,在併發讀寫中不須要加鎖。寫時複製容器適用於讀多寫少的場景,在複製時會佔用較多內存,可以保證最終一致性,但沒法保證瞬時一致性。

 

線程池

工具包中Executor接口定義了執行器的基本功能,即execute方法,接收Runnable對象參數並執行Runnable中的操做。

ExecutorService接口繼承Executor接口後增長了關於執行器服務的定義,如關閉、當即關閉、檢查關閉、等待終止、提交有返回值的任務、批量提交任務等。經過Executors的工廠方法獲取ExecutorService的具體實現,目前Executors能夠返回的實現類型以下:

FixedThreadPool:固定大小的線程池,建立時指定大小;

WorkStealingPool:擁有多個任務隊列(以便減小鏈接數)的線程池;

SingleThreadExecutor:單線程執行器,顧名思義只有一個線程執行任務;

CachedThreadPool:根據須要建立線程,能夠重複利用已存在的線程來執行任務;

SingleThreadScheduledExecutor:根據時間計劃延遲建立單個工做線程或者週期性建立的單線程執行器;

ScheduledThreadPool:可以延後執行任務,或者按照固定的週期執行任務。

若是但願在任務執行完成後獲得任務的返回值,能夠調用submit方法傳入Callable任務,並經過返回的Future對象查看任務執行是否完成,並獲取返回值。

 

線程分叉與合併

ForkJoinPool 讓咱們能夠很方便地把任務分裂成幾個更小的任務,這些分裂出來的任務也將會提交給 ForkJoinPool。任務能夠繼續分割成更小的子任務,只要它還能分割。分叉和合並原理包含兩個遞歸進行的步驟。兩個步驟分別是分叉步驟和合並步驟。

一個使用了分叉和合並原理的任務能夠將本身分叉(分割)爲更小的子任務,這些子任務能夠被併發執行。以下圖所示:

經過把本身分割成多個子任務,每一個子任務能夠由不一樣的 CPU 並行執行,或者被同一個 CPU 上的不一樣線程執行。

只有當給的任務過大,把它分割成幾個子任務纔有意義。把任務分割成子任務有必定開銷,所以對於小型任務,這個分割的消耗可能比每一個子任務併發執行的消耗還要大。

何時把一個任務分割成子任務是有意義的,這個界限也稱做一個閥值。這要看每一個任務對有意義閥值的決定。很大程度上取決於它要作的工做的種類。

當一個任務將本身分割成若干子任務以後,該任務將等待全部子任務結束。一旦子任務執行結束,該任務能夠把全部結果合併到同一個結果。圖示以下:

 

使用鎖實現的同步機制很像synchronized塊,可是比synchronized塊更靈活。鎖和synchronized的主要區別在於:

Synchronized塊不能保證等待進入塊的線程的訪問順序;

Synchronized塊沒法接收參數,不能在有超時時間限制的狀況下嘗試訪問;

Synchronized塊必須包含在單個方法中,而鎖的lock和unlock操做能夠在單獨的方法中。

工具包提供瞭如下幾種類型的鎖:

ReadWriteLock:讀寫鎖接口,容許多個線程讀取某個資源,可是一次只能有一個線程進行寫操做。內部有讀鎖、寫鎖兩個接口,分別保護讀操做和寫操做。實現類爲ReentrantReadWriteLock。

ReentrantLock:可重入鎖,具備與使用 synchronized 方法和語句所訪問的隱式監視器鎖定相同的一些基本行爲和語義,但功能更強大。ReentrantLock 將由最近成功得到鎖定,而且尚未釋放該鎖定的線程所擁有。當鎖定沒有被另外一個線程所擁有時,調用 lock 的線程將成功獲取該鎖定並返回。若是當前線程已經擁有該鎖定,此方法將當即返回。內部有一個計數器,擁有鎖的線程每鎖定一次,計數器加1,每釋放一次計數器減1。

 

原子類型

工具包提供了一些能夠用原子方式進行讀寫的變量類型,支持無鎖線程安全的單變量編程。

本質上,這些類都擴展了volatile的概念,使用一個volatile類型的變量來存儲實際數據。

工具包提供了4種類型的原子變量類型:

AtomicBoolean:可原子操做的布爾對象;

AtomicInteger:可原子操做的整形對象;

AtomicLong:可原子操做的長整形對象;

AtomicReference:可原子操做的對象引用。

以AtomicInteger爲例。在Java中i++和++i操做並非線程安全的,須要加鎖。AtomicInteger提供瞭如下幾種線程安全的操做方法:

方法 定義 做用
getAndIncrement public final int getAndIncrement() i++
getAndDecrement public final int getAndDecrement() i--
incrementAndGet public final int incrementAndGet() ++i
decrementAndGet() public final int decrementAndGet() --i
getAndAdd public final int getAndAdd(int delta) 增長delta返回舊值
addAndGet public final int addAndGet(int delta) 增長delta返回新值

在此基礎上,工具包還提供了原子性的數組類型,包括AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。

 

併發工具

CountDownLatch

CountDownLatch用於一個或者多個線程等待一系列指定操做的完成。初始化時,給定一個數量,每調用一次countDown() 方法數量減一。其餘線程調用await方法等待時,線程會阻塞到數量減到0纔開始執行。

 

CyclicBarrier 柵欄

CyclicBarrier是一種同步機制,它可以對處理一些算法的線程實現同步。換句話講,它就是一個全部線程必須等待的一個柵欄,直到全部線程都到達這裏,而後全部線程才能夠繼續作其餘事情。在下圖的流程中,線程1和線程2都到達第一個柵欄後纔可以繼續運行。若是線程1先到線程2後到,則線程1須要等待線程2到達柵欄處,而後兩個線程才能繼續運行。

 

Exchanger 交換機

Exchanger類表示一種會合點,兩個線程能夠在這裏交換對象。兩個線程各自調用exchange方法進行交換,當線程A調用Exchange對象的exchange()方法後,它會陷入阻塞狀態,直到線程B也調用了exchange()方法,而後以線程安全的方式交換數據,以後線程A和B繼續運行。

 

 

Semaphore 信號量

Semaphore 能夠很輕鬆完成信號量控制,Semaphore能夠控制某個資源可被同時訪問的個數,經過 acquire() 獲取一個許可,若是沒有就等待,而 release() 釋放一個許可。

 

ThreadLocalRandom產生併發隨機數

使用Math.random()產生隨機數,使用原子變量來保存當前的種子,這樣兩個線程同時調用序列時獲得的是僞隨機數,而不是相同數量的兩倍。ThreadLocalRandom提供併發產生的隨機數,可以解決多個線程發生的競爭爭奪。

 

原文地址:Java 8併發工具包漫遊指南

相關文章
相關標籤/搜索