那些年應該相識的執行緒安全集合們
- 2019 年 10 月 6 日
- 筆記
上篇推文介紹了List的三種實現其實都不是執行緒安全的,文章結尾也回答了如何創建執行緒安全的List,答案是:Collections.synchronizedList。
接下來小強再後台收到熱心童鞋(在此特別感謝並艾特三老師)回復了其他方式List執行緒安全的實現方式:CopyOnWriteArrayList。
本文就為大家總結下Concurrent下常用的執行緒安全集合們。主要包含以下兩種類型的集合:
– CopyOnWriteArrayList
– BlockingQueue/BlockingDeque
CopyOnWriteArryList
在使用CopyOnWriteArrayList之前,我們先閱讀其源碼了解下它是如何實現的:
public void add(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len); Object[] newElements; int numMoved = len - index; if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
傳統的List在多執行緒同時讀寫的時候會拋出java.util.ConcurrentModificationException,而CopyOnWriteArrayList是使用CopyOnWrite(寫時複製)技術解決了這個問題,上面程式碼是向CopyOnWriteArrayList中add方法的實現(向CopyOnWriteArrayList里添加元素),可以發現在添加的時候是需要加鎖的,否則多執行緒寫的時候會Copy出N個副本出來。這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更有效。
讀的時候不需要加鎖,如果讀的時候有多個執行緒正在向CopyOnWriteArrayList添加數據,讀還是會讀到舊的數據,因為寫的時候不會鎖住舊的CopyOnWriteArrayList
public E get(int index) { return get(getArray(), index); }
所以,通過上面的介紹,CopyOnWriteArrayList並發場景更適合於讀多寫少的場景。
BlokingQueue/BlockingDeque
阻塞隊列BlockingQueue是一個介面,BlockingDeque繼承了BlockingQueue,程式碼如下:
public interface BlockingQueue<E> extends Queue<E> { }
public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> { }
這裡兩個比較相似的阻塞隊列,BlockingQueue和BlockingDeque,兩個都是隊列,只不過前者只能一端出一端入,後者則可以兩端同時出入,並且他們的實現類都是結構改變執行緒安全的隊列。
BlockingQueue介面的常用實現有以下幾種:
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
BlockingDeque介面的常用實現有以下幾種:
- LinkedBlockingDeque
其實兩個隊列從實現思想上比較容易理解,有以下特點:
- 鏈表結構(動態數組)
- 通過ReentrantLock實現鎖
- 利用Condition實現隊列的阻塞等待,喚醒
下面以ArrayBlockingQueue的構造方法舉例來了解以上特點:
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
結語
其實還有一種很常見的執行緒安全結合ConcurrentHashMap。它是一種支援高並發、高吞吐量的執行緒安全HashMap實現,其中,在java8中對ConcurrentHashMap的結構進行了很大的改造。
以後的文章,小強可以詳細介紹這部分內容,因為這部分屬於JAVA面試中非常常見的問題,需要專門的篇幅來介紹。在此不贅述。
