1. 程式人生 > >並發容器1

並發容器1

true cursor hash 隊列 影響 中間 便是 1.2 核心

主要分析 List Map Set 中的 並發集合。 默認基於1.6分析

1 CopyOnWriteArrayList

juc包下的類;

該類是支持隨機訪問的List, 和Vector(同步鎖實現線程安全)和ArrayList(非線程安全)對照。

1.1 屬性

  1. transient final ReentrantLock lock = new ReentrantLock();
  2. private volatile transient Object[] array;

array 和ArrayList 一樣。 還是Object類型數組。volatile 保證該引用線程可見性。

對於array中數據的讀寫,此類都是通過函數getArray/setArray實現。

也就是簡單的get(i) 也不能直接返回array[i] 而是需要先通過getArray()獲取array然後使用索引獲取值。
技術分享

  • 為什麽這樣實現?
    HB規則? 利用volatile寫HB於volatile讀 ,包裝可見性?

1.2 構造函數

ArrayList 中默認數組大小10 ; 此處默認創建空的Object數組。

由於Arrays.ArrayList.toArray() 該方法返回的數組不是Object類型數組(數組本質類型不是引用)。 這屬於javaBug .所以集合中基於 數組存儲的類像ArrayList Vector等都需要做檢查,防止array指向非Obeject數組。
http://www.cnblogs.com/zhizhizhiyuan/p/3662371.html

  • 關於容量:
    1. ArrayList默認10長度的數組,add()當數組容量不足時,會一次性擴容一般1.5倍; 即ArrayList中數組長度!=實際元素。 所以存在trimToSize()函數將數組空位去除。
    2. 而該類數組元素==數組長度。 初始0,每次add修改數組就會創建新的數組,所以不涉及一次性擴容操作。

1.3 不可變函數

即對於數組元素不會修改的函數。

contains。indexOf。lastIndexOf。toArray()。get。containsAll().

這些函數對底層數組並不修改但是執行之前都必須使用getArray()獲取 內部的數組array進而操作!

  • equals():
    List 接口下的equals語義都一樣。 ArrayList中使用的是AbstractList
    中實現的方法。
    對象相同肯定true, 只有都是List才能比較(否則false), 對兩個List從前向後依次遍歷 出現不一致的元素就是False;

1.4 可變函數

以下的這些函數都會對數組發生修改,他們在執行時都會加鎖解鎖(獨占鎖),這些函數都會創建新的數組然後將array指向新的數組。

1.4.1 clone() :

該類實現的同樣是淺拷貝。 該類中默認會持有獨占鎖該變量是transient的,所以需要單獨克隆(初始化該變量)。

1.4.2 set() :
  1. 執行此函數 需要使用獨占鎖 加鎖解鎖
  2. 對於數組元素的修改,每次都是創建新的數組,然後使用setArray()更新數組的引用。
  3. 即使 數組值不做更新。那麽也要調用setArray()
    關於此處為什麽不更新數組引用還要調用setArray?
    A: 估計是為了HB規則使用,保證可見性。
    技術分享
1.4.3 add()

執行前後都要使用 獨占鎖 加鎖解鎖。
同樣該操作每次也會創建新的數組,然後直接更新數組引用。
對於數組使用都是使用getArray和setArray 不能直接操作該引用。

1.4.4 remove(int)

remove 同上。也是創建新的數組。修改array指向的數組。
removeAll(Collection<?>) 也是創建新的數組,將留下的元素放到新的數組中,最後修改array引用。

1.4.5 clear()

直接將數組指向新的空數組。

1.4.6 addIfAbsent()

該方法在ArrayList中沒有; 如果元素不存在就添加
addAllAbsent(Collection<? extends E>) 同理。

1.5 關於叠代器

  • ArrayList中有兩個叠代器,單向和雙向(List接口就是這樣規定);
    1. 單向叠代器支持 remove;
    2. 雙向叠代器支持 add remove;
    3. 但是多線程環境下會拋出異常。如果A在叠代過程中別的叠代器修改了List結構,那麽就會導致ConcurrentModificationException。

此類直接實現List接口, 所以自然也支持兩種叠代器。 但是該類實現時將這兩個叠代器使用一個類實現COWIterator

1.5.1 COWIterator叠代器:

支持雙向遍歷,不支持remove ,set, add

技術分享
技術分享

  • 屬性:
    1. cursor 語義和ArrayList中一樣,向後遍歷時指向下一個被訪問的元素。向前遍歷時cursor-1指向下一個遍歷的元素。
    2. snapshot數組內容快照,此處僅僅是傳入引用為何稱為快照?

A: 該類中不可變函數(不修改數組內容結構)並不會修改array引用, 而可變函數當add元素後就會修改該類中array引用指向新的數組。也就是當次叠代器在叠代過程中時,別的線程add修改底層數組,這只會導致array指向別的數組。而該叠代器snapshot指向的還是之前的數組,並不會受到影響。

技術分享

1.6 總結

該類本質就是對 能造成數組修改的函數 : 每次都創建新的數組,將array指向新的數組。

對於叠代器: 不支持修改數組操作。 不同的叠代器實現的效果就是保存當前 array的鏡像, 不會受到外部數組修改影響(對此也不可見)。

  • 場景

    1. 對數組的讀操作(contain等也是 不對數組修改) 遠大於 寫操作。 由於add等方法每次都會創建新的數組,所以開銷較大,當數組大時更是這樣。
    2. 叠代器是線程安全的。但是不支持修改操作,它遍歷的僅僅是當時的快照。多線程下是安全的
  • 線程安全:
    該類的線程安全是使用volatile+獨占鎖 實現。

    1. 該類內部是volatile數組。每次讀操作都會讀取該引用,每次修改都會創建新的數組然後修改該引用。 正是這樣volatile寫HB於volatile讀,保證每次讀取都能讀取到最新的數據。
    2. 獨占鎖: set add 等修改數組的函數 采用獨占鎖,保證互斥執行。
  • Q: 對於修改操作只能是使用創建新的數組這樣方式?
    A: volatile僅僅能保證該引用的可見性。juc中原子類也僅僅能提供對對象個別字段的原子更新。如果不使用此方法那麽我們就需要對get等讀方法采取加鎖以保證看到最新值。 此類使用此方法就是適合讀多寫少場景,所以寫操作開銷稍微大點。
    以上解釋有誤; 參見ConcurrentHashMap中對於每個Segment(相當於一個HashMap)中維護的 volatile Entry[] 如何保證讀寫可見性。 ????


2 CopyOnWriteArraySet

  • 使用:
    該描述和CopyOnWriteArrayList 幾乎一模一樣,只是此是Set
    技術分享

  • 繼承體系
    該類直接繼承自AbstractSet,該類並不像AbstractList中實現了許多方法,僅僅實現了個別方法。

2.1 構造函數

該類持有CopyOnWriteArrayList, 二者本身功能類似只是此類是Set不支持重復,那麽只要在add時控制重復元素即可。(實際上也是這麽做的使用addIfAbsent代替add 防止重復)
技術分享

  • Set 架構中,非並發的容器並沒有基於數組實現的類,HashSet等底層都是基於HashMap。該類屬於特例吧。
    技術分享

2.2 函數

此類基於CopyOnWriteArrayList(代理模式or 適配?)
該類僅僅是對add方法稍加修改使用 addIfAbsent防止重復,叠代器等實現都一樣,代理調用;

equals(Object): 實現Set版本的比較。無序比較。


3 ConcurrentHashMap

https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
http://www.infoq.com/cn/articles/ConcurrentHashMap

3.1 概述

  • 繼承體系
    該類還實現 ConcurrentMap 接口;
    該類和Map相比也就是增加了幾個方法。
    put(): 不管鍵值對存不存在 都會add到Map;
    putIfAbsent(): 只有鍵值對不存在時才會 add; 已經存在的不會修改;
    replace(K,V) : 和putIfAbsent相反,只有該映射存在時才會修改,不存在不會add;
    replace(K,V,V): K-V 全匹配時才會修改。
    技術分享

3.2 基本結構:

技術分享

  1. 基於分段鎖提高並發: ConcurrentHashMap本質是將map劃分為多個區域(Segment), 每個區域等價於一個HashMap(該Map本身就持有一個鎖) 也就是在此類中對於get put這種操作: 首先是依據hashcode定位segment區域然後繼續在該區域中找對應位置。
  2. 該類中主要有四種函數

    • 構造函數;
    • isEmpty(),size() ,containsValue(); 由於ConcurrentHashMap采用分段,而在外層類中並沒有保存關於當前Map總數等信息,這些計數都存放到各自Segment,所以對於這三個函數區別分析;
    • get() , put(), contain(), remove(), clear() ; 這幾種函數大部分都是采用先定位Segment然後再在該區域執行操作。
    • keySet() ,values(),entrySet() 三種視圖 + enumeration叠代器
  3. 該類中鍵值 均不允許使用null, hashMap中允許;

3.2.1 字段

  • segments : 就是分割區域的數組.
  • segmentMask 和 segmentShift : 二者是用來定位segment區域的。構造函數中會初始化此變量。
    segment數組長度默認16,(它也會選取2的N次冪作為長度,方便hash定位區域,和HashMap類似) 簡單來說當get(O)時:

    1. 首先hash(o)再hash得到hashcode.
    2. 利用這個hashcode要確定區域segmnet和區域內所在的桶。
    3. 所以當segment數組長為16時:segmentMask 就是0x000...1111, segmentShift=28, 也就是取hashcode的最高4位來判斷所在的segment區域。
  • 三個視圖變量: AbstractMap中以及有了兩個視圖變量(本來是volatile),此處覆蓋後取消volatile;(而在HashMap中直接使用抽象類中定義的volatile變量。)
    一個Map對象中,這三個變量實質上都是只有一個實例。也並不涉及引用的改變,所以也沒必要volatile?
    技術分享

3.3.2 構造函數

和HashMap相比,此函數多了一個參數concurrencyLevel;

技術分享

  • concurrencyLevel: 大意就是制定當前Map應該分為幾個區域。默認16最大個數為2^16. 為了hash方便此也只能是2的整次冪。
  • loadFactor: 由於ConcurrentHashMap外層類中並不記錄當前Map中的總個數。Map的容量調整也都是發生在各自的Segment區域中, 所以該變量直接針對的就是每個segment中的負載因子,而不是針對全局的Map存放而言
  • initialCapacity: 和HashMap一樣,該指定的是桶的個數(也是2的次冪),最大和HashMap一樣2^30,默認16.
    1. 由於此外層類中並不存儲實際的,桶實際是在各個segmrnt中。 利用initialCapacity/區域個數(即concurrencyLevel) 來計算每個區域平均應該存放多少個桶。
    2. 註意: ConcurrentHashMap中雖然指定最大桶(initialCapacity)的數目是2^30, 並不是指全部桶只能這麽大, 每個區域各自擴容時上限2^30. 將每個區域視為一個hashMap即可。

3.3.3 hash定位

對get() , put(), contain(), remove(), clear() ; 這些函數。 外層類中主要工作就是找到該元素所在區域segment, 然後在該區域中執行操作. 核心就是區域定位

get:

  1. 技術分享
  2. 技術分享
    1. 首先就是hash() 方法再次hash.(防止對象本身hashcode分布不均勻)
    2. segmentFor 確定區域; 例如當前共有64個區域那麽segmentShift 就是32-6; 而segmentMask = 0x111111; 效果就是按照hashcode的最高6位來直接確定區域segment的索引。

3.3 Segment

該類就是一個Map中的一個區域, 功能等於HashMap, 只是get put 程安全。

每個區域相對於別的區域都是獨立的 ,ConcurrentHashMap 正是將Map劃分為不同的區域才能提供並發。(縮小沖突範圍)

該類的實現基本和HashMap一致。 此處選取個別方法分析 如get put;

3.3.1 結構:

  1. 該類為ReentrantLock子類,也就是該segment本身就能當作鎖來使用。
    在get等讀操作中不會鎖,只有put等寫操作才會采用鎖。 而且各個區域Segment持有各自不同的鎖。
    技術分享

  2. 變量:

    • 作為HashMap該類持有變量threshold,table,loadFactor 等; 各個區域之間是獨立的,每個區域維護各自的Map變量。擴容操作也是在各個區域單獨進行。
    • transient int modCount; : 和HashMap一樣,該變量記錄Map結構修改次數。 HashMap中使用此變量實現fail-fast機制,而此類並不用此機制。 此變量主要是用來ConcurrentHashMap.size().(外部類中並不記錄總的映射個數,利用此變量提高並發,見size()分析)
    • volatile變量: 該類將這兩個變量設置為volatile,利用volatile寫讀的內存語義,盡可能減少鎖的使用,實現內存可見性。 例如在每次get總會讀取volatile count, 每次put總會 寫入 volatile count, 這樣保證get在不加鎖的情況下 讀取到table中的最新值。 volatile寫HB於volatile讀,即前者對後者可見。
      技術分享

3.3.2 hash 與擴容:

  • hash : 和HashMap中一致: return tab[hash & (tab.length - 1)]; 都是直接利用hashcode直接確定。如當前segment中桶為32,即依據hashcode的低5位直接判斷所在的桶;

  • rehash() : 此函數只在put()中被調用,而put每次執行需要加鎖解鎖,所以此函數不會出現多個線程同時執行。

    1. 容量變化:和HashMap一樣 容量2倍
    2. 首先會創建新的table數組,然後遍歷舊的Map,將對象掛載到對應的桶中。
    3. 假設當前桶的長度是8也就是從000-111, 那麽每次使用hashcode的低3位便可以確定所在的桶。 假設現在擴容為16長度也即使用末尾4位判斷桶位置。
      所以對於原來某個桶中的元素(如0號):只會被分到0號或者8號桶中;

    4. 該函數執行過程中會盡量使用原有的Entry. 但是絕對不會破話原有Entery的結構。 實際上HashEntry的next引用也被設置為final防止修改。

  • 如何保證rehash()執行過程中,get線程不會讀取到錯誤的值?
    A: 當創建新的table後,此函數會遍歷舊的table,clone原有的對象,並保證不會破壞舊的結構。 當函數執行完畢後才會將table指向新的數組; 也就是在此時間段內get訪問的還是舊的table.
    對於一些HashEntry如果能利用那麽該函數會盡可能利用:例如0號桶中原來之掛載了HashEntry e,那麽此對象就完全可以重新利用, 沒必要clone. 總之就是不修改原有Entry next引用,實際中也無法修改,從而實現保護舊的table直到新的table創建完成
    技術分享

3.3.3 get(Object, int)

get操作不需要獲取鎖,這也是ConcurrentHashMap能高效並發的原因!

該類中不允許key-value為null;

技術分享

  1. count作用:
    A: count是volatile變量。在每次get前都會讀取,而在每次put後都會執行寫入操作。這樣做是利用volatile內存語義。保證上次put操作的修改能夠對此get可見。
    在contain等函數中同樣每次都會讀取count;

  2. 利用傳入的hash定位桶,在該桶中變量查找鍵值對然後返回。segment中不允許鍵值對為null,為什麽此處v可能為null?
    A: 寫操作put是需要持有鎖的,而get不需要。put執行時對於已經存在的鍵直接修改value即可, 對於新的鍵需要創建新的HashEntry。由於put和get並沒有同步。所以就會造成get定位到HashEntry時該對象還沒有完成初始化。
    此時就需要嘗試通過獲取鎖來獲取value值。readValueUnderLock函數
    技術分享
    技術分享

  • Q: 關於get put多線程環境下可見性,當一個線程put多個線程get時,count的可見性語義僅僅能保證此時刻之前的操作可見, 但是get畢竟不是原子操作。所以get可能會讀取到舊的值,但是肯定不會是異常值? 而HashTable中get put 全部持有鎖,造成執行串行話,但是HashTable肯定是能看到最新的值。 所以此處get並不是嚴格地能夠獲取到最新值??
    A: get弱一致性; 以上說的情況的確可能會發生;

    api中也提到 Retrievals reflect the results of the most recently completed update operations holding upon their onset.

    由於get操作無鎖,所以他能觀測到put中途執行結果,無鎖並發,就是把原來的競爭塊變成了點,為成了一個一個的點,就不存在沖突了,並且前後順序的關鍵點不是原來的數據寫入或讀取,而是用來同步的標記之類的讀寫,上面就是count變量的讀寫,是一種思維的轉變。如果要保證一個並發工具,在執行某個操作後保持該狀態,那這就是鎖的使用場景
    但是從宏觀角度put方法執行完之後,get肯定能看到put的結果;
    http://ifeve.com/concurrenthashmap-weakly-consistent/

3.3.4 put(K, int, V, boolean)

  1. put操作必須持有鎖。
  2. 當Segment中鍵值對數量超過閾值,需要調用rehash擴容 各個區域的擴容獨立進行。
  3. put對於已經存在的鍵值對: 直接修改value
  4. put對於新的鍵值對:只能從鏈表的頭部插入。 因為HashEntry的next引用被設置為final,所以只能創建新的HashEntry指向head;
  5. 每次創建新的Entry後,modCount++ 表示Map結構的調整。 該變量會被用在外部類size()中。
  6. 此函數支持外部類put和putIfAbsent調用。 (後者只有在舊值存在才會修改)
  • 關於讀寫並發?
    A: 參照3.3.3中討論,1寫+多讀如何保證不會出現異常。

  • 當創建新的Entry後 寫入volatile 變量 count. 但是修改value時為什麽寫入該變量呢???

3.3.5 remove(Object, int, Object)

  1. remove 操作是對Map的修改,所以同樣需要持有鎖。
  2. 對Map結構修改後同樣會記錄 modCount++; 並寫入volatile count , 保證get函數調用時可見性。
  3. 刪除HashEntry過程中,盡可能利用之前的HashEntry節點,但是鏈表順序可能改變。
  4. remove函數主要是被外部類remove函數調用。包括只匹配鍵和鍵值全匹配兩個版本;
  • remove如何刪除Entry?
    A: e就表示要刪除的Entry.如圖C是要刪除的Node,A是鏈表head. 此時D E節點完全可以重復利用,C之前的AB 由於next引用為final不能修改,所以不能引用所以必須創建新的Entry. 從A開始依次遍歷頭插到DE隊列中。 當BADE新的鏈表創建好後,直接修改table[index]
    技術分享
    多線程環境下,如果一個線程在remove,別的線程在get, remove在刪除過程中並不會修改原有的鏈表。只是在創建好新的鏈表後直接修改table[index],並寫入volatile count遍歷保證get線程可見此修改。
    此和copyOnwriteArrayList中方法類似。

3.3.6 :contain ,replace

  • contain: 包含兩個版本使用key或者value.

    1. contain(key): key不存在是否為null, 該函數只要直接定位桶遍歷該桶即可。
    2. containsValue(): 需要對Map遍歷查找。該函數和get類似,遍歷過程中可能看到沒有初始化完成的Entry,此時和get一樣需要獲取鎖來獲取value;
  • replace() 也是兩種,直接使用key替換,或者基於key-value。 需要持有鎖

    1. 兩種方法類似,都是ConcurrentMap中定義方法。
  • clear(): 需要持有鎖。直接將table[i]設置為null。

3.4 modCount使用

外部類ConcurrentHashMap中不記錄總的鍵值對,Map修改次數等。這些都是記錄在各自區域Segment中。
在HashMap中modCount被用來實現fail-fast機制。 而此處並沒有此機制。

  • size() isEmpty() containsValue() 都會使用modCount盡可能不對Map加鎖來實現功能;
    containsKey能直接定位區域而containsValue需要掃描全局Map.

3.4.1 size() 實現

size需要讀取各個區域的count變量,最暴力的方法便是一次性對所有區域加鎖,然後計算count總和然後釋放全部鎖。但是這樣代價太高
技術分享

ConcurrentHashMap中充分利用各個區域的modCount(記錄該區域Map結構修改次數)

  1. 首先他會連續計算兩次 count總和 和 各個區域的modCount。
  2. 如果兩次的count和 各個區域的modCount 都相等,說明在此期Map沒有發生改變。 所以該count總和是準確的。
  3. 如果以上兩次便利的 count和不一樣 說明此期間Map發生改變。然後就執行以上暴力方法加鎖統計

ConcurrentHashMap會對以上不加鎖的統計方法嘗試兩次
技術分享

3.4.2 isEmpty()

該函數僅僅是判斷空,所以只要某個區域count!=0 就能返回false.

技術分享

  1. 該函數第一次遍歷:如果發現某個count!=0那麽必然非空Map。 遍歷過程中記錄各個區域的modCount. 如果該總和為0 說明此時Map還沒有put任何元素。此時Map是空的。
  2. 第二次遍歷: 除了判斷count!=0外,還會比較再此期間modCount是否發生變化. 如果count是0但是modcount發生變化那麽此也被視為非空Map.
  • ABA 問題:
    假如區域A 在第一次遍歷時count = 0, modCount = 1; 第二次遍歷 count = 0, modCount = 100;
    如果僅僅依靠modCount那麽就認為Map沒有改變,即Map為空(即使中間經歷了許多操作ABA問題 , 0再次被視為同一狀態,但是二者其實是不同狀態。)
    一般對於ABA問題,例如原子類中, 一般是狀態綁定時間戳int值來表示二者狀態變化。 此處就可以將modCount視為時間戳變量 前後兩次的遍歷可以認為是不同狀態。

  • 該函數可靠性?
    A: 由於該函數並沒有持有鎖,所以其必然沒有HashTable那樣的可信度。 也就是即使我們兩次for循環遍歷認為當前Map為空, 但是在執行return之前還可能發生許多次的put操作。(雖然可能性比較小。)
    該類中許多函數都是這樣。由於不加鎖,所以並不具有絕對的語義??

3.4.2 containsValue

該函數同上類似,只是涉及到全局Map遍歷查找。

3.5 視圖+叠代器

視圖: keySet()。values()。values()。
Enumeration: keys。elements()。

視圖:

視圖概念和HashMap一致,都是對底層的Map數據操作。 各個視圖都支持使用iterator返回各自視圖的叠代器。

  • 這三種叠代器核心都是HashIterator: 叠代器不支持fail-fast 不會拋出異常
  • 在遍歷過程中,如果已經遍歷的數組上的內容變化了,叠代器不會拋出ConcurrentModificationException異常。如果未遍歷的數組上的內容發生了變化,則有可能反映到叠代過程中。這就是ConcurrentHashMap叠代器弱一致的表現。

和HashMap相比,此類涉及到分區域Segment, 所以叠代器實現時需要記錄更多信息。

  1. abstract class HashIterator {
  2. int nextSegmentIndex; //下一個需要遍歷的區域編號
  3. int nextTableIndex; // 當前區域內,下一個需要遍歷的 桶編號
  4. HashEntry<K,V>[] currentTable;
  5. HashEntry<K, V> nextEntry; // 下一個需要遍歷的Entry
  6. HashEntry<K, V> lastReturned;

next和remove: 不支持fail-fast, 所以也不必檢查modCount等變量。remove函數雖然不是同步的但是它調用的底層remove函數需要獲取獨占鎖,所以此操作是線程安全的。

advance() : 用來獲取一個Entry後調整nextSegmentIndex,nextTableIndex,nextEntry等變量方便下次操作。
技術分享

Enumeration

本質也是叠代器,只是不支持remove操作。 對於keys和elements 都是返回各自的叠代器。 和各自視圖中使用相同的叠代器。

在此類中Enumeration和Iterator 遍歷效果一模一樣,調用函數都是一樣的;


3.6 總結

ConcurrentHashMap 是一個並發散列映射表的實現,它允許完全並發的讀取,並且支持給定數量的並發更新。相比於 HashTable 和用同步包裝器包裝的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 擁有更高的並發性。在 HashTable 和由同步包裝器包裝的 HashMap 中,使用一個全局的鎖來同步不同線程間的並發訪問。同一時間點,只能有一個線程持有鎖,也就是說在同一時間點,只能有一個線程能訪問容器。這雖然保證多線程間的安全並發訪問,但同時也導致對容器的訪問變成_串行化_的了。

ConcurrentHashMap 的高並發性主要來自於三個方面

  1. 用分離鎖實現多個線程間的更深層次的共享訪問。
  2. 用 HashEntery 對象的不變性來降低執行讀操作的線程在遍歷鏈表期間對加鎖的需求。
  3. 通過對同一個 Volatile 變量的寫 / 讀訪問,協調不同線程間讀 / 寫操作的內存可見性。(get put前後count讀寫)
  • 關於HashEntery對象不變性:
    1. HashEntry 中的 key,hash,next 都聲明為 final 型。這意味著,不能把節點添加到鏈接的中間和尾部,也不能在鏈接的中間和尾部刪除節點。這個特性可以保證:在訪問某個節點時,這個節點之後的鏈接不會被改變。這個特性可以大大降低處理鏈表時的復雜性。
    2. 查看remove put等函數的實現: 在修改Map結構時,總是采取不修改原有鏈表結構,colne原有Entry(對於能夠復用的盡量復用), 當結構修改好後直接修改Tbale[i]引用。 這樣使得在remove,put等操作時get線程不會受到影響,他們讀取的還是舊的鏈表結構
      https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

3.6.1 關於弱一致性

http://ifeve.com/concurrenthashmap-weakly-consistent/
get, clear() 以及叠代器等, 都是弱一致性。

clear: 因為沒有全局的鎖,在清除完一個segments之後,正在清理下一個segments的時候,已經清理segments可能又被加入了數據,因此clear返回的時候,ConcurrentHashMap中是可能存在數據的。因此,clear方法是弱一致的。
get() :3.3.3;

  • ConcurrentHashMap的弱一致性主要是為了提升效率,是一致性與效率之間的一種權衡。要成為強一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。


Task && Q

  1. 關於CopyOnWriteArrayList為什麽每次總是創建新的數組保證可見性? 即1.6分析有誤! 到底是為了叠代器視圖還是為了get set並發可見性。 如果僅僅是可見性完全可以類似ConcurrentHashMap Segment中 每次get set 讀寫 volatile count變量, 進而保證內存可見性?
  2. 3.3.3 中問題, get和put多線程下 可見性》
    A: put方法執行完之後,get肯定能看到put的結果

  3. 3.3.4 中put函數count變量為什麽不在 修改value地方寫入,而僅僅在創建新的節點後寫入??? 這樣能保證get可見性????

  4. 3.4.2 可靠性? ConcurrentMap是犧牲一些可靠性來換取 並發??

遺留:

  1. ConcurrentNavigableMap 接口
  2. ConcurrentSkipListMap 類
  3. ConcurrentSkipListSet 類 和2類似;

並發容器1