Java並發編程(三) 並發類庫中的常用類


1. 同步容器類

  遺留下來的同步容器類包括Vector和Hashtable,此外java.util.Collections類中還提供了以下工廠方法創建線程安全的容器對象:

  Collections.synchronizedList 返回支持同步操作(線程安全)的List對象;

  Collections.synchronizedSet 返回支持同步操作(線程全的)的Set對象;

  Collections.synchronizedMap 返回支持同步操作(線程安全)的Map對象;

  需要注意的是,同步容器類中的每一個單獨的方法都是線程安全的,但對這些方法的復合操作仍然需要額外的客戶端加鎖來保護。另外,同步容器類對象執行每個操作期間都會持有一個鎖,這會導致吞吐量大大降低。例如:

List<String>  synList = Collections.synchronizedList(list);

synchronized(synList)
{
   for(int i = 0; i < synList.size(); i++)
     System.out.println(synList.get(i));
}

  對synList對象調用size方法和get方法需要用客戶端鎖來保護,並且在這個迭代中,需要頻繁的加鎖和釋放鎖,吞吐量很低。 使用迭代器對同步容器類對象進行迭代也是需要加鎖的,否則在迭代時若有其他線程修改則會拋出ConcurrentModificationException異常。    

 

2. 並發容器類

  並發容器類對同步容器類的性能進行了較大的改進:

  (1)並發容器類提供了一些常用的表示復合操作的方法,並能確保這些復合操作是線程安全的。

    例如:ConcurrentHashMap.putIfAbsent方法,相當於:

      if(!map.contains(key))

        map.put(key,value);

  (2)並發容器類可以大幅度提高並發容器類上操作的吞吐量。

  (3)並發容器類的迭代器和普通的迭代器不一樣,這些迭代器具有弱一致性,不具有“fast-fail”特性,不會拋出ConcurrentModificationException。

  常見的並發容器類有:

  ConcurrentLinkedQueue表示並發的隊列;BlockingQueue表示阻塞隊列,增加了可阻塞的入隊和出隊操作。如果隊列為空,則出隊操作阻塞,對於有界隊列來說,如果隊列滿了則入隊操作阻塞。阻塞隊列可以方便地用於實現“生產者-消費者”模式。

  CopyOnWriteArrayList表示並發的線性表,它通過在每個線程對底層List作出修改時復制整個底層List來實現並發的線程安全,這需要很大的開銷,但是對於多線程環境下遍歷為主要操作的List來說是很有用的。CopyOnWriteArraySet與CopyOnWriteList類似。

  ConcurrentHashMap表示並發的映射表,ConcurrentHashMap並沒有像Hashtable和synchronized的Map一樣加獨占鎖,它使用了更細粒度的分段鎖機制,可以大幅提高並發的吞吐量。但是有些方法的返回結果是不准確的,如:size方法和isEmpty方法返回的並不是實時的狀態。如果在多線程環境中需要對Map對象獨占式的訪問,就不應該使用ConcurrentHashMap類。

 

3. 工作密取

  在“生產者—消費者”模式中,生產者和消費者共享一個隊列,而在工作密取的情境中,每個消費者都有一個雙端隊列,在消費者完成了自己隊列中的工作時,可以去其他消費者隊列的隊尾取來工作,而並不會干擾其他消費者的工作。在工作密取情境中,消費者從自己隊列的隊頭取自己的工作,從其他消費者的隊尾取別人的工作來完成。

  工作密取非常適合於消費者同時也是生產者的情形,當消費者執行工作時發現有更多的工作要做,則可以將這些工作放到自己隊列的末尾,也可以送到其他消費者隊列的隊尾;當自己隊列沒有工作要做時,可以去其他消費者隊列取工作來完成,這樣每個消費者都會保持忙碌的狀態。

  工作密取可以使用Deque(雙端隊列)來完成,ArrayDeque和LinkedList是非阻塞雙端隊列的實現;BlockingDeque表示阻塞的雙端隊列,putFirst、takeFirst、putLast、takeLast等方法實現了阻塞地訪問隊頭或者隊尾,LinkedBlockingDeque是阻塞雙端隊列的實現。

 

4. 同步工具類

  同步工具類的作用是根據其自身的狀態來協調多線程的運行,同步工具類封裝了一些狀態,這些狀態將決定某些線程是否阻塞,還提供了一些方法對這些狀態進行操作。

  常見的同步工具類有:阻塞隊列、閉鎖、信號量、柵欄。

  (1)閉鎖

  閉鎖可以延遲線程的進度直到閉鎖到達終止狀態,可以用來確保某些活動直到其他某個活動都完成后才繼續執行。例如:

  確保某個計算在其需要的所有資源都准備好之后才執行;

  確保某個服務在其依賴的所有服務都啟動后才啟動;

  等待直到某個操作的所有參與者就緒;

  CountDownLatch是常用的閉鎖的實現,CountDownLatch包括一個計數器,countDown方法將計數器減1,表示有一個事件發生了,await方法使調用線程阻塞,直到計數器為0,表示等待的所有事件都已經發生。

  FutureTask也可以作為閉鎖,FutureTask的計算任務通過Callable來實現,Callable接口有一個call方法,可以返回計算的結果。FutureTask提供了get方法,調用get方法試圖獲得計算結果,任務已完成則立即獲取結果,否則將阻塞直到計算結束。

  (2)信號量

  信號量可以用來控制同時訪問某個資源的線程數量,可以用來實現資源池。

  Semaphore是信號量的實現,Semaphore維護着一定數量的許可,在執行操作時可以首先通過acquire方法獲得許可,如果許可已經用完則阻塞,操作執行完后通過release方法釋放許可。

  許可數目為1的信號量可以作為互斥信號量,並且是不可重入的。

  (3)柵欄

  柵欄和閉鎖很相似,主要區別在於柵欄延遲線程的進度直到所有線程都准備好以后。

  CyclicBarrier是柵欄的實現,線程調用await方法到達柵欄處,若該線程不是到達柵欄處的最后一個線程,則被阻塞。若該線程是到達柵欄處的最后一個線程,則不會阻塞,並且柵欄釋放,所有的線程都可以繼續執行。

  所有線程都釋放之后,柵欄將被重置,柵欄非常適合於並行迭代算法。

  

 

參考資料 《Java並發編程實戰》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM