本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營連接:http://item.jd.com/12299018.htmlhtml
上節咱們介紹了ConcurrentHashMap,ConcurrentHashMap不能排序,容器類中能夠排序的Map和Set是TreeMap和TreeSet,但它們不是線程安全的。Java併發包中與TreeMap/TreeSet對應的併發版本是ConcurrentSkipListMap和ConcurrentSkipListSet,本節,咱們就來簡要探討這兩個類。git
基本概念github
咱們知道,TreeSet是基於TreeMap實現的,與此相似,ConcurrentSkipListSet也是基於ConcurrentSkipListMap實現的,因此,咱們主要來探討ConcurrentSkipListMap。算法
ConcurrentSkipListMap是基於SkipList實現的,SkipList稱爲跳躍表或跳錶,是一種數據結構,待會咱們會進一步介紹。併發版本爲何採用跳錶而不是樹呢?緣由也很簡單,由於跳錶更易於實現高效併發算法。編程
ConcurrentSkipListMap有以下特色:swift
看段簡單的使用代碼:數組
public static void main(String[] args) { Map<String, String> map = new ConcurrentSkipListMap<>( Collections.reverseOrder()); map.put("a", "abstract"); map.put("c", "call"); map.put("b", "basic"); System.out.println(map.toString()); }
程序輸出爲:安全
{c=call, b=basic, a=abstract}
表示是有序的。微信
ConcurrentSkipListMap的大部分方法,咱們以前都有介紹過,有序的方法,與TreeMap是相似的,原子複合操做,與ConcurrentHashMap是相似的,因此咱們就不贅述了。數據結構
須要說明一下的是它的size方法,與大多數容器實現不一樣,這個方法不是常量操做,它須要遍歷全部元素,複雜度爲O(N),並且遍歷結束後,元素個數可能已經變了,通常而言,在併發應用中,這個方法用處不大。
下面咱們主要介紹下其基本實現原理。
基本實現原理
咱們先來介紹下跳錶的結構,跳錶是基於鏈表的,在鏈表的基礎上加了多層索引結構。咱們經過一個簡單的例子來看下,假定容器中包含以下元素:
3, 6, 7, 9, 12, 17, 19, 21, 25, 26
對Map來講,這些值能夠視爲鍵。ConcurrentSkipListMap會構造相似下圖所示的跳錶結構:
最下面一層,就是最基本的單向鏈表,這個鏈表是有序的。雖然是有序的,但咱們知道,與數組不一樣,鏈表不能根據索引直接定位,不能進行二分查找。
爲了快速查找,跳錶有多層索引結構,這個例子中有兩層,第一層有5個節點,第二層有2個節點。高層的索引節點必定同時是低層的索引節點,好比9和21。
高層的索引節點少,低層的多,統計機率上,第一層索引節點是實際元素數的1/2,第二層是第一層的1/2,逐層減半,但這不是絕對的,有隨機性,只是大概如此。
對於每一個索引節點,有兩個指針,一個向右,指向下一個同層的索引節點,另外一個向下,指向下一層的索引節點或基本鏈表節點。
有了這個結構,就能夠實現相似二分查找了,查找元素老是從最高層開始,將待查值與下一個索引節點的值進行比較,若是大於索引節點,就向右移動,繼續比較,若是小於,則向下移動到下一層進行比較。
下圖兩條線展現了查找值19和8的過程:
對於19,查找過程是:
對於8,查找過程是:
這個結構是有序的,查找的性能與二叉樹相似,複雜度是O(log(N)),不過,這個結構是如何構建起來的呢?
與二叉樹相似,這個結構是在更新過程當中進行保持的,保存元素的基本思路是:
對於索引更新,隨機計算一個數,表示爲該元素最高建幾層索引,一層的機率爲1/2,二層爲1/4,三層爲1/8,依次類推。而後從最高層到最低層,在每一層,爲該元素創建索引節點,建的過程也是先查找位置,再插入。
對於刪除元素,ConcurrentSkipListMap不是一會兒真的進行刪除,爲了不併發衝突,有一個複雜的標記過程,在內部遍歷元素的過程當中會真正刪除。
以上咱們只是介紹了基本思路,爲了實現併發安全、高效、無鎖非阻塞,ConcurrentSkipListMap的實現很是複雜,具體咱們就不探討了,感興趣的讀者能夠參考其源碼,其中提到了多篇學術論文,論文中描述了它參考的一些算法。
對於常見的操做,如get/put/remove/containsKey,ConcurrentSkipListMap的複雜度都是O(log(N))。
上面介紹的SkipList結構是爲了便於併發操做的,若是不須要併發,可使用另外一種更爲高效的結構,數據和全部層的索引放到一個節點中,以下圖所示:
對於一個元素,只有一個節點,只是每一個節點的索引個數可能不一樣,在新建一個節點時,使用隨機算法決定它的索引個數,平均而言,1/2的元素有兩個索引,1/4的元素有三個索引,依次類推。
小結
本節簡要介紹了ConcurrentSkipListMap和ConcurrentSkipListSet,它們基於跳錶實現,有序,無鎖非阻塞,徹底並行,主要操做複雜度爲O(log(N))。
下一節,咱們來探討併發隊列。
(與其餘章節同樣,本節全部代碼位於 https://github.com/swiftma/program-logic)
----------------
未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),從入門到高級,深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。