ConcurrentHashMapjava
ConcurrentHashMap底層具體實現算法
JDK 1.7底層實現數組
將數據分爲一段一段的存儲,而後給每一段數據配一把鎖, 當一個線程佔用鎖訪問其中一個段數據時,其餘段的數據也能被其餘線程訪問。安全
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。 其中Segment 實現了 ReentrantLock,因此Segment是一種可重入鎖,扮演鎖的角色。 HashEntry 用於存儲鍵值對數據。數據結構
一個ConcurrentHashMap裏包含一個Segment數組。 Segment結構和HashMap相似,是一種數組和鏈表結構, 一個Segment包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護着一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到對應的Segment的鎖。多線程
JDK 1.8底層實現併發
TreeBin: 紅黑二叉樹節點 Node: 鏈表節點dom
ConcurrentHashMap取消了Segment分段鎖,採用CAS和synchronized來保證併發安全。 數據結構與HashMap1.8的結構相似,數組+鏈表/紅黑二叉樹(鏈表長度>8時,轉換爲紅黑樹)。分佈式
synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要hash值不衝突,就不會產生併發。ide
ConcurrentHashMap和Hashtable的區別
底層數據結構:
JDK1.7 的ConcurrentHashMap底層採用分段的數組+鏈表實現, JDK1.8 的ConcurrentHashMap底層採用的數據結構與JDK1.8 的HashMap的結構同樣,數組+鏈表/紅黑二叉樹。
Hashtable和JDK1.8 以前的HashMap的底層數據結構相似都是採用數組+鏈表的形式, 數組是 HashMap 的主體,鏈表則是主要爲了解決哈希衝突而存在的
實現線程安全的方式
JDK1.7的ConcurrentHashMap(分段鎖)對整個桶數組進行了分割分段(Segment), 每一把鎖只鎖容器其中一部分數據,多線程訪問容器裏不一樣數據段的數據,就不會存在鎖競爭,提升併發訪問率。 JDK 1.8 採用數組+鏈表/紅黑二叉樹的數據結構來實現,併發控制使用synchronized和CAS來操做。
Hashtable:使用 synchronized 來保證線程安全,效率很是低下。 當一個線程訪問同步方法時,其餘線程也訪問同步方法,可能會進入阻塞或輪詢狀態, 如使用 put 添加元素,另外一個線程不能使用 put 添加元素,也不能使用 get,競爭會愈來愈激烈。
HashTable全表鎖
ConcurrentHashMap分段鎖
CopyOnWriteArrayList
public class CopyOnWriteArrayList<E> extends Object
implements List<E>, RandomAccess, Cloneable, Serializable
在不少應用場景中,讀操做可能會遠遠大於寫操做。 因爲讀操做根本不會修改原有的數據,所以對於每次讀取都進行加鎖實際上是一種資源浪費。 咱們應該容許多個線程同時訪問List的內部數據,畢竟讀取操做是安全的。 這和ReentrantReadWriteLock讀寫鎖的思想很是相似,也就是讀讀共享、寫寫互斥、讀寫互斥、寫讀互斥。 JDK中提供了CopyOnWriteArrayList類比相比於在讀寫鎖的思想又更進一步。 爲了將讀取的性能發揮到極致,CopyOnWriteArrayList 讀取是徹底不用加鎖的,而且寫入也不會阻塞讀取操做。 只有寫入和寫入之間須要進行同步等待。這樣,讀操做的性能就會大幅度提升。
CopyOnWriteArrayList的實現機制
CopyOnWriteArrayLis 類的全部可變操做(add,set等等)都是經過建立底層數組的新副原本實現的。 當 List 須要被修改的時候,我並不修改原有內容,而是對原有數據進行一次複製,將修改的內容寫入副本。 寫完以後,再將修改完的副本替換原來的數據,這樣就能夠保證寫操做不會影響讀操做了。
CopyOnWriteArrayList是知足CopyOnWrite的ArrayList, 所謂CopyOnWrite也就是說: 在計算機,若是你想要對一塊內存進行修改時,咱們不在原有內存塊中進行寫操做, 而是將內存拷貝一份,在新的內存中進行寫操做,寫完以後呢,就將指向原來內存指針指向新的內存, 原來的內存就能夠被回收掉了。
CopyOnWriteArrayList讀取和寫入源碼簡單分析
CopyOnWriteArrayList讀取操做的實現
讀取操做沒有任何同步控制和鎖操做, 由於內部數組array不會發生修改,只會被另一個array替換,所以能夠保證數據安全。
/** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; public E get(int index) { return get(getArray(), index); } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } final Object[] getArray() { return array; }
CopyOnWriteArrayList讀取操做的實現
CopyOnWriteArrayList 寫入操做 add() 方法在添加集合的時候加了鎖, 保證同步,避免了多線程寫的時候會複製出多個副本出來。
/**
* Appends the specified element to the end of this list. */ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock();//加鎖 try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//拷貝新數組 newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock();//釋放鎖 } }
ConcurrentLinkedQueue
Java提供的線程安全的 Queue 能夠分爲阻塞隊列和非阻塞隊列,其中阻塞隊列的典型例子是 BlockingQueue, 非阻塞隊列的典型例子是ConcurrentLinkedQueue,在實際應用中要根據實際須要選用阻塞隊列或者非阻塞隊列。 阻塞隊列能夠經過加鎖來實現,非阻塞隊列能夠經過CAS操做實現。
ConcurrentLinkedQueue使用鏈表做爲其數據結構。 ConcurrentLinkedQueue應該算是在高併發環境中性能最好的隊列了。 它之全部能有很好的性能,是由於其內部複雜的實現。
ConcurrentLinkedQueue 主要使用CAS非阻塞算法來實現線程安全。 適合在對性能要求相對較高,對個線程同時對隊列進行讀寫的場景, 即若是對隊列加鎖的成本較高則適合使用無鎖的ConcurrentLinkedQueue來替代。
BlockingQueue
java.util.concurrent.BlockingQueue 接口有如下阻塞隊列的實現:
FIFO 隊列 :LinkedBlockingQueue、ArrayBlockingQueue(固定長度)
優先級隊列 :PriorityBlockingQueue
提供了阻塞的 take() 和 put() 方法:若是隊列爲空 take() 將阻塞,直到隊列中有內容; 若是隊列爲滿 put() 將阻塞,直到隊列有空閒位置。
使用 BlockingQueue 實現生產者消費者問題
public class ProducerConsumer { private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5); private static class Producer extends Thread { @Override public void run() { try { queue.put("product"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print("produce.."); } } private static class Consumer extends Thread { @Override public void run() { try { String product = queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.print("consume.."); } } }
public static void main(String[] args) { for (int i = 0; i < 2; i++) { Producer producer = new Producer(); producer.start(); } for (int i = 0; i < 5; i++) { Consumer consumer = new Consumer(); consumer.start(); } for (int i = 0; i < 3; i++) { Producer producer = new Producer(); producer.start(); } }
produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..
免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門:https://mp.weixin.qq.com/s/Jz...