框架是個好東西,可早晚有一天會過時,這世界上就沒有亙古不變的東西,來學下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數據類型。