1. 程式人生 > >Java學習筆記—多線程(同步容器和並發容器)

Java學習筆記—多線程(同步容器和並發容器)

clas href blank post sts 代碼 線程 包括 ear

簡述同步容器與並發容器

  在Java並發編程中,經常聽到同步容器、並發容器之說,那什麽是同步容器與並發容器呢?同步容器可以簡單地理解為通過synchronized來實現同步的容器,比如Vector、Hashtable以及SynchronizedList等容器,如果有多個線程調用同步容器的方法,它們將會串行執行。

  可以通過查看Vector、Hashtable等同步容器的實現代碼,可以看到這些容器實現線程安全的方式就是將它們的狀態封裝起來,並在需要同步的方法上加上關鍵字synchronized,但在某些情況下,同步容器不一定就是線程安全的,比如獲取最後一個元素或者刪除最後一個元素,我們需要實現額外的同步操作:

public static Object getLast(Vector list) {  
    int lastIndex = list.size() - 1;  
    return list.get(lastIndex);  
}  
      
public static void deleteLast(Vector list) {  
    int lastIndex = list.size() - 1;  
    list.remove(lastIndex);  
} 

  雖然上面的方法看起來沒有問題,Vector自身的方法也是同步的,但是在多線程環境中還是隱藏著問題。如果有兩個線程A,B同時調用上面的兩個方法,假設list的大小為10,這裏計算得到的lastIndex為9,線程B首先執行了刪除操作(多線程之間操作執行的不確定性導致),而後線程A調用了list.get方法,這時就會發生數組越界異常,導致問題的原因就是上面的復合操作不是原子操作,這裏可以通過在方法內部使用list對象鎖來實現原子操作。

  同步容器會導致多個線程中對容器方法調用的串行執行,降低並發性,因為它們都是以容器自身對象為鎖,所以在需要支持並發的環境中,可以考慮使用並發容器來替代。並發容器是針對多個線程並發訪問而設計的,在jdk5.0引入了concurrent包,其中提供了很多並發容器,如ConcurrentHashMap、CopyOnWriteArrayList等。

  其實同步容器與並發容器都為多線程並發訪問提供了合適的線程安全,不過並發容器的可擴展性更高。在Java5之前,程序員們只有同步容器,且在多線程並發訪問的時候會導致爭用,阻礙了系統的擴展性。Java5介紹了並發容器,並發容器使用了與同步容器完全不同的加鎖策略來提供更高的並發性和伸縮性,例如,在ConcurrentHashMap中采用了一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,允許任意數量的讀線程並發地訪問map,並且執行讀操作的線程和寫操作的線程也可以並發的訪問map,同時允許一定數量的寫操作線程並發地修改map,所以它可以在並發環境下實現更高的吞吐量,另外,並發容器提供了一些在使用同步容器時需要自己實現的復合操作,包括putIfAbsent等,但是由於並發容器不能通過加鎖來獨占訪問,所以我們無法通過加鎖來實現其他復合操作了。

並發容器

由上面的分析我們知道,同步容器並不能保證多線程安全,而並發容器是針對多個線程並發訪問而設計的,在jdk5.0引入了concurrent包,其中提供了很多並發容器,極大的提升同步容器類的性能。

ConcurrentHashMap

  • 對應的非並發容器:HashMap
  • 目標:代替Hashtable、synchronizedMap,支持復合操作
  • 原理:JDK6中采用一種更加細粒度的加鎖機制Segment“分段鎖”,JDK8中采用CAS無鎖算法,詳細分析推薦閱讀【JDK】:ConcurrentHashMap高並發機制——【轉載】。

CopyOnWriteArrayList

  • 對應的非並發容器:ArrayList
  • 目標:代替Vector、synchronizedList
  • 原理:利用高並發往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先復制一份新的集合,在新的集合上面修改,然後將新集合賦值給舊的引用,並通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。

關於這一部分可參考【JDK】:CopyOnWriteArrayList、CopyOnWriteArraySet 源碼解析

CopyOnWriteArraySet

  • 對應的費並發容器:HashSet
  • 目標:代替synchronizedSet
  • 原理:基於CopyOnWriteArrayList實現,其唯一的不同是在add時調用的是CopyOnWriteArrayList的addIfAbsent方法,其遍歷當前Object數組,如Object數組中已有了當前元素,則直接返回,如果沒有則放入Object數組的尾部,並返回。

關於這一部分可參考【JDK】:CopyOnWriteArrayList、CopyOnWriteArraySet 源碼解析

ConcurrentSkipListMap

  • 對應的非並發容器:TreeMap
  • 目標:代替synchronizedSortedMap(TreeMap)
  • 原理:Skip list(跳表)是一種可以代替平衡樹的數據結構,默認是按照Key值升序的。Skip list讓已排序的數據分布在多層鏈表中,以0-1隨機數決定一個數據的向上攀升與否,通過”空間來換取時間”的一個算法。ConcurrentSkipListMap提供了一種線程安全的並發訪問的排序映射表。內部是SkipList(跳表)結構實現,在理論上能夠在O(log(n))時間內完成查找、插入、刪除操作。

ConcurrentSkipListSet

  • 對應的非並發容器:TreeSet
  • 目標:代替synchronizedSortedSet
  • 原理:內部基於ConcurrentSkipListMap實現

ConcurrentLinkedQueue

不會阻塞的隊列

  • 對應的非並發容器:Queue
  • 原理:基於鏈表實現的FIFO隊列(LinkedList的並發版本)

LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue

    • 對應的非並發容器:BlockingQueue
    • 特點:拓展了Queue,增加了可阻塞的插入和獲取等操作
    • 原理:通過ReentrantLock實現線程安全,通過Condition實現阻塞和喚醒
    • 實現類:
      • LinkedBlockingQueue:基於鏈表實現的可阻塞的FIFO隊列
      • ArrayBlockingQueue:基於數組實現的可阻塞的FIFO隊列
      • PriorityBlockingQueue:按優先級排序的隊列

參考:

java並發:同步容器&並發容器

【JDK】:Java容器框架——同步容器與並發容器

Java學習筆記—多線程(同步容器和並發容器)