1. 程式人生 > >【JDK】:Java容器框架——同步容器與併發容器

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

前面的文章中詳細介紹了Java的容器框架,在此基礎上,本文對Java中的同步容器與併發容器做一些介紹。

fail-fast機制

快速報錯機制(fail-fast)能夠防止多個程序同時修改同一個容器的內容。如果在你迭代遍歷某個容器的過程中,另一個程序接入其中,並且插入、刪除或者修改此容器內的某個物件,就會出現問題:也許迭代過程已經處理過容器中的該元素了,也許還沒處理,也許在呼叫size()之後尺寸縮小了等等。fail-fast機制會探查容器上的任何除了你的程序所進行的操作以外的所有變化,一旦它發現其他程序修改了容器,立刻丟擲ConcurrentModificationException異常,即快速報錯——不適用複雜的演算法在時候進行檢查。

同步容器

同步容器可以簡單地理解為通過synchronized來實現同步的容器,如果有多個執行緒呼叫同步容器的方法,它們將會序列執行。

同步容器將它們的狀態封裝起來,並對每一個公有方法進行同步。主要包括:

  • Vector
  • Stack
  • HashTable
  • Collections.synchronized方法生成,例如:
    • Collectinons.synchronizedList()
    • Collections.synchronizedSet()
    • Collections.synchronizedMap()
    • Collections.synchronizedSortedSet()
    • Collections.synchronizedSortedMap()

其中Vector(同步的ArrayList)和Stack(繼承自Vector,先進後出)、HashTable(繼承自Dictionary,實現了Map介面)是比較老的容器,Thinking in Java中明確指出,這些容器現在仍然存在於JDK中是為了向以前老版本的程式相容,在新的程式中不應該在使用。Collections的方法時將非同步的容器包裹生成對應的同步容器。

同步容器在單執行緒的環境下能夠保證執行緒安全,但是通過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物件鎖來實現原子操作。

在多執行緒中使用同步容器,如果使用Iterator迭代容器或使用使用for-each遍歷容器,在迭代過程中修改容器會丟擲ConcurrentModificationException異常。想要避免出現ConcurrentModificationException,就必須在迭代過程持有容器的鎖。但是若容器較大,則迭代的時間也會較長。那麼需要訪問該容器的其他執行緒將會長時間等待。從而會極大降低效能。

此外,隱式迭代的情況,如toString,hashCode,equalse,containsAll,removeAll,retainAll等方法都會隱式的Iterate,也可能丟擲ConcurrentModificationException。

併發容器

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

ConcurrentHashMap

CopyOnWriteArrayList

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

CopyOnWriteArraySet

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

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:按優先順序排序的佇列