ConcurrentHashMap
1、hashtable、hashmap、ConcurrentHashMap
一、線程不安全的HashMap數組
由於多線程環境下,使用Hashmap進行put操做會引發死循環,致使CPU利用率接近100%,因此在併發狀況下不能使用HashMap。安全
二、效率低下的HashTable數據結構
HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的狀況下HashTable的效率很是低下。由於當一個線程訪問HashTable的同步方法時,其餘線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,而且也不能使用get方法來獲取元素,因此競爭越激烈效率越低。多線程
三、鎖分段技術併發
HashTable容器在競爭激烈的併發環境下表現出效率低下的緣由,是由於全部訪問HashTable的線程都必須競爭同一把鎖,那假如容器裏有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裏不一樣數據段的數據時,線程間就不會存在鎖競爭,從而能夠有效的提升併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術。高併發
2、ConcurrentHashMap的數據結構
ConcurrentHashMap爲了提升自己的併發能力,在內部採用了一個叫作Segment的結構,一個Segment其實就是一個類Hash Table的結構,同HashMap同樣,Segment包含一個HashEntry數組,數組中的每個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。能夠說,ConcurrentHashMap是一個二級哈希表。在一個總的哈希表下面,有若干個子哈希表。性能
ConcurrentHashMap定位一個元素的過程須要進行兩次Hash操做,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部,所以,這一種結構的帶來的反作用是Hash的過程要比普通的HashMap要長,可是帶來的好處是寫操做的時候能夠只對元素所在的Segment進行加鎖便可,不會影響到其餘的Segment,這樣,在最理想的狀況下,ConcurrentHashMap能夠最高同時支持Segment數量大小的寫操做(恰好這些寫操做都很是平均地分佈在全部的Segment上),因此,經過這一種結構,ConcurrentHashMap的併發能力能夠大大的提升。優化
3、Segment
Segment繼承了ReentrantLock,因此它就是一種可重入鎖(ReentrantLock)。在ConcurrentHashMap,一個Segment就是一個子哈希表,Segment裏維護了一個HashEntry數組,併發環境下,對於不一樣Segment的數據進行操做是不用考慮鎖競爭的。網站
4、get、put、remove
Get方法:spa
1.爲輸入的Key作Hash運算,獲得hash值。
2.經過hash值,定位到對應的Segment對象
3.再次經過hash值,定位到Segment當中數組的具體位置。
Put方法:
1.爲輸入的Key作Hash運算,獲得hash值。
2.經過hash值,定位到對應的Segment對象
3.獲取可重入鎖
4.再次經過hash值,定位到Segment當中數組的具體位置。
5.插入或覆蓋HashEntry對象。
6.釋放鎖。
Remove方法:
定位元素通put方法,不過這裏刪除元素的方法不是簡單地把待刪除元素的前面的一個元素的next指向後面一個就完事了,因爲HashEntry中的next是final的,一經賦值之後就不可修改,在定位到待刪除元素的位置之後,程序就將待刪除元素前面的那一些元素所有複製一遍,而後再一個一個從新接到鏈表上去
5、size()方法
1.遍歷全部的Segment。
2.把Segment的元素數量累加起來。
3.把Segment的修改次數累加起來。
4.判斷全部Segment的總修改次數是否大於上一次的總修改次數。若是大於,說明統計過程當中有修改,從新統計,嘗試次數+1;若是不是。說明沒有修改,統計結束。
5.若是嘗試次數超過閾值,則對每個Segment加鎖,再從新統計。
6.再次判斷全部Segment的總修改次數是否大於上一次的總修改次數。因爲已經加鎖,次數必定和上次相等。
7.釋放鎖,統計結束。
爲了儘可能不鎖住全部Segment,首先樂觀地假設Size過程當中不會有修改。當嘗試必定次數,才無奈轉爲悲觀鎖,鎖住全部Segment保證強一致性。
6、jdk1.7和1.8中的區別
JDK1.8的實現已經摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,併發控制使用Synchronized和CAS來操做,整個看起來就像是優化過且線程安全的HashMap,雖然在JDK1.8中還能看到Segment的數據結構,可是已經簡化了屬性,只是爲了兼容舊版本
改進一:取消segments字段,直接採用transient volatile HashEntry<K,V>[] table保存數據,採用table數組元素做爲鎖,從而實現了對每一行數據進行加鎖,進一步減小併發衝突的機率。
改進二:將原先table數組+單向鏈表的數據結構,變動爲table數組+單向鏈表+紅黑樹的結構。對於hash表來講,最核心的能力在於將key hash以後能均勻的分佈在數組中。若是hash以後散列的很均勻,那麼table數組中的每一個隊列長度主要爲0或者1。但實際狀況並不是老是如此理想,雖然ConcurrentHashMap類默認的加載因子爲0.75,可是在數據量過大或者運氣不佳的狀況下,仍是會存在一些隊列長度過長的狀況,若是仍是採用單向列表方式,那麼查詢某個節點的時間複雜度爲O(n);所以,對於個數超過8(默認值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度能夠下降到O(logN),能夠改進性能。
CopyOnWriteArrayList
1、arraylist的問題
多線程時,若是遍歷過程當中另外一個線程對list進行插入之後,遍歷報錯。
2、CopyOnWriteArrayList的實現原理
CopyOnWrite容器即寫時複製的容器。通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器。這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。
3、CopyOnWrite的應用場景
CopyOnWrite併發容器用於讀多寫少的併發場景。好比白名單,黑名單,商品類目的訪問和更新場景,假如咱們有一個搜索網站,用戶在這個網站的搜索框中,輸入關鍵字搜索內容,可是某些關鍵字不容許被搜索。這些不能被搜索的關鍵字會被放在一個黑名單當中,黑名單天天晚上更新一次。當用戶搜索時,會檢查當前關鍵字在不在黑名單當中,若是在,則提示不能搜索。
4、缺點
1.內存佔用問題。由於CopyOnWrite的寫時複製機制,因此在進行寫操做的時候,內存裏會同時駐紮兩個對象的內存,舊的對象和新寫入的對象(注意:在複製的時候只是複製容器裏的引用,只是在寫的時候會建立新對象添加到新容器裏,而舊容器的對象還在使用,因此有兩份對象內存)。
2.數據一致性問題。CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。因此若是你但願寫入的的數據,立刻能讀到,請不要使用CopyOnWrite容器。
5、總結
1.線程安全,讀操做時無鎖的ArrayList
2.底層數據結構是一個Object[],初始容量爲0,以後每增長一個元素,容量+1,數組複製一遍
3.增刪改上鎖、讀不上鎖
4.遍歷過程因爲遍歷的只是全局數組的一個副本,即便全局數組發生了增刪改變化,副本也不會變化,因此不會發生併發異常
5.讀多寫少且髒數據影響不大的併發狀況下,選擇CopyOnWriteArrayList