Java併發容器,底層原理深刻分析

ConcurrentHashMapjava

ConcurrentHashMap底層具體實現算法

JDK 1.7底層實現數組

clipboard.png

將數據分爲一段一段的存儲,而後給每一段數據配一把鎖, 當一個線程佔用鎖訪問其中一個段數據時,其餘段的數據也能被其餘線程訪問。安全

ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。 其中Segment 實現了 ReentrantLock,因此Segment是一種可重入鎖,扮演鎖的角色。 HashEntry 用於存儲鍵值對數據。數據結構

一個ConcurrentHashMap裏包含一個Segment數組。 Segment結構和HashMap相似,是一種數組和鏈表結構, 一個Segment包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護着一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到對應的Segment的鎖。多線程

JDK 1.8底層實現併發

TreeBin: 紅黑二叉樹節點 Node: 鏈表節點dom

clipboard.png

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全表鎖

clipboard.png

ConcurrentHashMap分段鎖

clipboard.png

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...

相關文章
相關標籤/搜索