共计 3448 个字符,预计需要花费 9 分钟才能阅读完成。
前言
不思考多线程并发的状况下,容器类个别应用 ArrayList、HashMap 等线程不平安的类,效率更高。在并发场景下,常会用到 ConcurrentHashMap、ArrayBlockingQueue 等线程平安的容器类,尽管就义了一些效率,但却失去了平安。
下面提到的线程平安容器都在 java.util.concurrent 包下,这个包下并发容器不少,明天全副翻出来鼓捣一下。
仅做简略介绍,后续再别离深刻摸索。
- ConcurrentHashMap:并发版 HashMap
- CopyOnWriteArrayList:并发版 ArrayList
- CopyOnWriteArraySet:并发 Set
- ConcurrentLinkedQueue:并发队列 (基于链表)
- ConcurrentLinkedDeque:并发队列 (基于双向链表)
- ConcurrentSkipListMap:基于跳表的并发 Map
- ConcurrentSkipListSet:基于跳表的并发 Set
- ArrayBlockingQueue:阻塞队列 (基于数组)
- LinkedBlockingQueue:阻塞队列 (基于链表)
- LinkedBlockingDeque:阻塞队列 (基于双向链表)
- PriorityBlockingQueue:线程平安的优先队列
- SynchronousQueue:读写成对的队列
- LinkedTransferQueue:基于链表的数据交换队列
- DelayQueue:延时队列
1.ConcurrentHashMap 并发版 HashMap
最常见的并发容器之一,能够用作并发场景下的缓存。底层仍然是哈希表,但在 JAVA 8 中有了不小的扭转,而 JAVA 7 和 JAVA 8 都是用的比拟多的版本,因而常常会将这两个版本的实现形式做一些比拟(比方面试中)。
一个比拟大的差别就是,JAVA 7 中采纳分段锁来缩小锁的竞争,JAVA 8 中放弃了分段锁,采纳 CAS(一种乐观锁),同时为了避免哈希抵触重大时进化成链表(抵触时会在该地位生成一个链表,哈希值雷同的对象就链在一起),会在链表长度达到阈值(8)后转换成红黑树(比起链表,树的查问效率更稳固)。
2.CopyOnWriteArrayList 并发版 ArrayList
并发版 ArrayList,底层构造也是数组,和 ArrayList 不同之处在于:当新增和删除元素时会创立一个新的数组,在新的数组中减少或者排除指定对象,最初用新增数组替换原来的数组。
实用场景:因为读操作不加锁,写(增、删、改)操作加锁,因而实用于读多写少的场景。
局限:因为读的时候不会加锁(读的效率高,就和一般 ArrayList 一样),读取的以后正本,因而可能读取到脏数据。如果介意,倡议不必。
看看源码感触下:
3.CopyOnWriteArraySet 并发 Set
基于 CopyOnWriteArrayList 实现(内含一个 CopyOnWriteArrayList 成员变量),也就是说底层是一个数组,意味着每次 add 都要遍历整个汇合能力晓得是否存在,不存在时须要插入(加锁)。
实用场景:在 CopyOnWriteArrayList 实用场景下加一个,汇合别太大(全副遍历伤不起)。
4.ConcurrentLinkedQueue 并发队列 (基于链表)
基于链表实现的并发队列,应用乐观锁 (CAS) 保障线程平安。因为数据结构是链表,所以实践上是没有队列大小限度的,也就是说增加数据肯定能胜利。
5.ConcurrentLinkedDeque 并发队列 (基于双向链表)
基于双向链表实现的并发队列,能够别离对头尾进行操作,因而除了先进先出 (FIFO),也能够先进后出(FILO),当然先进后出的话应该叫它栈了。
6.ConcurrentSkipListMap 基于跳表的并发 Map
SkipList 即跳表,跳表是一种空间换工夫的数据结构,通过冗余数据,将链表一层一层索引,达到相似二分查找的成果
7.ConcurrentSkipListSet 基于跳表的并发 Set
相似 HashSet 和 HashMap 的关系,ConcurrentSkipListSet 外面就是一个 ConcurrentSkipListMap,就不细说了。
8.ArrayBlockingQueue 阻塞队列 (基于数组)
基于数组实现的可阻塞队列,结构时必须制订数组大小,往里面放货色时如果数组满了便会阻塞直到有地位(也反对间接返回和超时期待),通过一个锁 ReentrantLock 保障线程平安。
乍一看会有点纳闷,读和写都是同一个锁,那要是空的时候正好一个读线程来了不会始终阻塞吗?
答案就在 notEmpty、notFull 里,这两个出自 lock 的小东西让锁有了相似 synchronized + wait + notify 的性能。传送门 → 终于搞懂了 sleep/wait/notify/notifyAll
9.LinkedBlockingQueue 阻塞队列 (基于链表)
基于链表实现的阻塞队列,想比与不阻塞的 ConcurrentLinkedQueue,它多了一个容量限度,如果不设置默认为 int 最大值。
10.LinkedBlockingDeque 阻塞队列 (基于双向链表)
相似 LinkedBlockingQueue,但提供了双向链表特有的操作。
11.PriorityBlockingQueue 线程平安的优先队列
结构时能够传入一个比拟器,能够看做放进去的元素会被排序,而后读取的时候按程序生产。某些低优先级的元素可能长期无奈被生产,因为一直有更高优先级的元素进来。
12.SynchronousQueue 数据同步替换的队列
一个虚伪的队列,因为它实际上没有真正用于存储元素的空间,每个插入操作都必须有对应的取出操作,没取出时无奈持续放入。
import java.util.concurrent.SynchronousQueue;
public class Main {
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
new Thread(()->{
try{
for(int i=0;;i++){
System.out.println(“ 放入:” + i);
queue.put(i);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}).start();
new Thread(()->{
try{
while(true){
System.out.println(“ 取出:” + queue.take());
Thread.sleep((long)(Math.random()*2000));
}
}catch (InterruptedException e){
e.printStackTrace();
}
}).start();
}
}
运行后果:
取出:0
放入:0
取出:1
放入:1
放入:2
取出:2
取出:3
放入:3
取出:4
放入:4
…
…
能够看到,写入的线程没有任何 sleep,能够说是全力往队列放货色,而读取的线程又很不踊跃,读一个又 sleep 一会。输入的后果却是读写操作成对呈现。
JAVA 中一个应用场景就是 Executors.newCachedThreadPool(),创立一个缓存线程池。
13.LinkedTransferQueue 基于链表的数据交换队列
实现了接口 TransferQueue,通过 transfer 办法放入元素时,如果发现有线程在阻塞在取元素,会间接把这个元素给期待线程。如果没有人等着生产,那么会把这个元素放到队列尾部,并且此办法阻塞直到有人读取这个元素。和 SynchronousQueue 有点像,但比它更弱小。
14.DelayQueue 延时队列
能够使放入队列的元素在指定的延时后才被消费者取出,元素须要实现 Delayed 接口。
总结
下面简略介绍了 JAVA 并发包下的一些容器类,晓得有这些货色,遇到适合的场景时就能想起有个现成的货色能够用了。想要知其所以然,后续还得再深刻摸索一番。
起源:https://www.cnblogs.com/java-friend/p/11675772.html
如果大家对 java 架构相干感兴趣,能够关注公众号 ” 架构殿堂 ”,会继续更新 java 根底面试题, netty, spring boot,spring cloud 等系列文章,一系列干货随时送达, 超神之路从此开展, BTAJ 不再是幻想!