Java基礎(十三)

框架是個好東西,可早晚有一天會過時,這世界上就沒有亙古不變的東西,來學下Java基礎吧

JDK提供的併發容器

  • ConcurrentHashMap:線程安全的HashMap
  • CopyOnWriteArrayList:線程安全的List,讀多寫少的場合性能非常好,取代了Vector
  • ConcurrentLinkedQueue:高效的併發隊列,可以看做一個併發的LinkedList,是一個非阻塞隊列
  • BlockingQueue:阻塞隊列的接口,JDK內部實現了,非常適合用於數據共享的通道
  • ConcurrentSkipListMap:跳錶的實現,是一個Map,可以進行快速的查找

ConcurrentHashMap

HashMap是線程不安全的,如果我們要保證線程安全,可以使用Collections.synchronizedMap()方法來實現對Map的包裝,相當於對HashMap加了一個全局鎖,非常影響性能【HashTable的做法】,而隨之而來的ConcurrentHashMap無論是在讀操作和寫操作都大大的提高了性能,在讀操作中基本不會出現加鎖的情況,而寫操作也是通過分段鎖的技術來保證了很高的性能

至於ConcurrentHashMap和HashTable以及他的底層實現原理,在前面已經說過【Java基礎(九)】

CopyOnWriteArrayList

1.簡介:

應該有這樣的思想:讀操作不會改變原有的數據,所以每次對讀取操作進行加鎖其實是一種浪費。要有ReentrantReadWriteLock讀寫鎖的:讀讀共享,寫寫互斥,讀寫互斥的思想。而JDK提供的CopyOnWriteArrayList就是最好的實現,將性能發揮到了極致,在讀取的時候是完全不會進行加鎖的,寫入的時候也不會進行阻塞讀取的操作,只有寫入和寫入之間會阻塞,所以CopyOnWriteArrayList的讀取性能遠遠優越於寫入性能

2.實現原理:

對CopyOnWriteArrayList數據修改的操作並不會直接修改原有內容,而是將原有內容複製出來,修改複製的內容,修改完成後再將原有內容進行覆蓋(⚠️並不是真正的覆蓋)​,和線程操作主內存的思路一樣,這樣就爆炸了寫操作不會影響讀操作了。

CopyOnWriteArrayList是滿足CopyWrite的ArrayList,而CopyWrite指的就是在計算機中,如果想要對內存進行修改,不會直接修改原有內存,而是複製原有內存的數據,在新內存中進行寫入操作,完成之後指針就從原有內存指向了新內存,原來的內存即被回收掉了

3.源碼分析

    final transient Object lock = new Object();
    private transient volatile Object[] array;

    final Object[] getArray() {
        return this.array;
    }

    final void setArray(Object[] a) {
        this.array = a;
    }

讀取都沒有加鎖,原因是讀寫不會在同一塊內存中出現,所以不會出現信息不對稱的情況

    public boolean add(E e) {
        synchronized(this.lock) {
            Object[] es = this.getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            this.setArray(es);
            return true;
        }
    }

寫入操作加了鎖,是爲了防止多個線程同時Copy多個副本出來,造成信息不對稱

ConcurrentLinkedQueue

Java提供的線程安全的隊列可以分爲阻塞的和非阻塞的,阻塞隊列的代表就是BlockingQueue,非阻塞的代表例子就是ConcurrentLinkedQueue,兩者並無明顯的優劣,選用時要根據實際情況而定,阻塞隊列可以通過加鎖來實現,非阻塞隊列可以通過CAS(樂觀鎖)操作來實現

ConcurrentLinkedQueue底層使用鏈表來作爲底層的數據結構,ConcurrentLinkedQueue應該是高併發環境下性能表現最好的Queue的,原因在於他內部的複雜實現

2.實現原理:

比較複雜,不好分析,只要知道是通過CAS來實現非阻塞的就行了

3.適用場景:

對性能要求較高,同時隊列的讀寫存在於多個線程同時進行的情況

BlockingQueue【接口】

1.簡單介紹:

阻塞隊列被廣泛用於“生產者–消費者”的問題中,當隊列已經滿了,生產者線程會被阻塞,直到隊列不滿爲止。當隊列爲空時,消費者線程會被阻塞,直到隊列非空

2.繼承結構:

Collection<–Queue<–BlockingQueue

主要實現類爲:ArrayBlockingQueue,LinkedBlockingQueue和priorityBlockingQueue

3.實現類簡介:

1)ArrayBlockingQueue:底層採用數組,定長,採用可重入鎖來控制,默認是非公平鎖,也可以指定爲公平鎖,代碼如下:

    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        } else {
            this.items = new Object[capacity];
            this.lock = new ReentrantLock(fair);
            this.notEmpty = this.lock.newCondition();
            this.notFull = this.lock.newCondition();
        }
    }

2)LinkedBlockingQueue:底層採用單向鏈表,可以做無界隊列也可以當有界隊列來使用,與ArrayBlockingQueue相比有更高的吞吐量,爲了防止容量激增,內存損耗嚴重,可以在創建時候就指定max,如果不指定則隊列最長可達到2147483647【接近於無界】,構造方法如下:

    public LinkedBlockingQueue() {
        this(2147483647);
    }

    public LinkedBlockingQueue(int capacity) {
        this.count = new AtomicInteger();
        this.takeLock = new ReentrantLock();
        this.notEmpty = this.takeLock.newCondition();
        this.putLock = new ReentrantLock();
        this.notFull = this.putLock.newCondition();
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        } else {
            this.capacity = capacity;
            this.last = this.head = new LinkedBlockingQueue.Node((Object)null);
        }
    }

3)PriorityBlockingQueue:是一個支持優先級的無界阻塞隊列,默認使用自然規則排序,也可以通過自定義類實現compareTo方法來實現元素之間的比較排序,或者初始化時候指定Comparator來指定排序規則

PriorityBlockingQueue是通過重入鎖來控制併發的,隊列爲無界隊列,創建對象傳入的int值是隊列初始化長度,如果空間不夠就會自動擴充,構造方法摘要如下:

	public PriorityBlockingQueue() 
    public PriorityBlockingQueue(int initialCapacity)
    public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
    public PriorityBlockingQueue(Collection<? extends E> c)

ConcurrentSkipListMap

1.簡單介紹:

對於一個普通單鏈表,要查詢到指定下標的數據,效率會比較低,而跳錶可以用來快速查找的數據結構,結構類似於平衡樹,而他們兩者的主要區別則是:平衡樹的插入和刪除往往都會導致平衡樹重新進行一次全局的調整,而跳錶的插入與刪除僅僅只需要對部分數據進行操作。

這樣帶來的好處是:在高併發環境下:平衡樹需要鎖住整個數據結構,效率較低,而跳錶只需要鎖住部分數據結構,可以允許多線程同時訪問跳錶的多個地址,JDK使用跳錶實現了ConcurrentSkipListMap

2.跳錶特點

跳錶的最大特點就是用空間來換取時間,與普通的HashMap不同的是:HashMap並不會維持元素的順序,而跳錶會維持元素的順序,所以當需要有序的時候,使用ConcurrentSkipListMap是不二之選

用法:把他當成普通的map使用即可。跳錶的數據結構還需要了解

ConcurrentSkipListSet的底層即使用了ConcurrentSkipListMap數據類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章