文章目录
1 并发容器
1.1 ConcurrentHashMap
为什么要引入ConcurrentHashMap?
HashMap是线程非安全的在多线程的环境下会有安全问题,HashTable虽然是线程安全的但是synchronized关键字是加在方法上的,锁的粒度太大,多线程情况下虽然保证了安全但是效率太低。
HashMap具体情况如何?(JDK1.8)
HashMap的put()流程图(图片来源于网络,侵权删图):
HashMap的get():
1 首先将 key hash 之后取得所定位的桶。
2 如果桶为空则直接返回 null 。
3 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
4 如果第一个不匹配,则判断它的下一个是红黑树还是链表。
5 红黑树就按照树的查找方式返回值。
6 不然就按照链表的方式遍历匹配返回值。
为什么重写equal要重写hashcode?
1.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率,比如HashSet判断是否重复的时候,如果重写equal没重写hashcode这个类不能配合hashset这些容器使用
2.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
ConcurrentHashMap
<jdk1.7>:数组(Segment) + 数组(HashEntry) + 链表(HashEntry节点),底层一个Segments数组,存储一个Segments对象,一个Segments中储存一个Entry数组,存储的每个Entry对象又是一个链表头结点。
<jdk1.8>:Node数组+链表 / 红黑树: 类似hashMap<jdk1.8>,Node数组使用来存放树或者链表的头结点,当一个链表中的数量到达一个数目时,会使查询速率降低,所以到达一定阈值时,会将一个链表转换为一个红黑二叉树,通告查询的速率。在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。
1.2 阻塞队列
参考文献链接
阻塞队列其实也就是队列的一种特殊情况。举个例子来说明一下吧,我们去餐馆吃饭,一个接一个的下单,这时候就是一个普通的队列,万一这家店生意好,餐馆挤满了人,这时候肯定不能把顾客赶出去,于是餐馆就在旁边设置了一个休息等待区。这就是一个阻塞队列了。我们使用一张图来演示一下:
特点:
(1)当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,
(2)当阻塞队列满了,往队列添加元素的操作将会被阻塞。
由于这个特点,阻塞队列广泛应用于生产者消费者问题。
常见的几种BlockingQueue
ArrayBlockingQueue和LinkedBlockingQueue是最为常用的阻塞队列,前者使用一个有边界的数组来作为存储介质,而后者使用了一个没有边界的链表来存储数据。(有界)
PriorityBlockingQueue是一个优先阻塞队列。所谓优先队列,就是每次从队队列里面获取到的都是队列中优先级最高的,对于优先级,PriorityBlockingQueue需要你为插入其中的元素类型提供一个Comparator,PriorityBlockingQueue使用这个Comparator来确定元素之间的优先级关系。底层的数据结构是堆,也就是我们数据结构中的那个堆。(无界)
DelayQueue是一个延时队列,所谓延时队列就是消费线程将会延时一段时间来消费元素。
SynchronousQueue是最为复杂的阻塞队列。SynchronousQueue和前面分析的阻塞队列都不同,因为SynchronousQueue不存在容量的说法,任何插入操作都需要等待其他线程来消费,否则就会阻塞等待,看到这种队列心里面估计就立马能联想到生产者消费者的这种模式了,没错,就可以使用这个队列来实现。
提供的方法:
插入元素:
add(e):抛出异常
offer(e):返回特殊值
put(e):一直阻塞
offer(e,time,unit):超时退出
移除元素:
remove():抛出异常
poll():返回特殊值
take():一直阻塞
poll(time,unit):超时退出
public class BlockingQueue_Test {
private static final int MAX_CAPACITY = 10;
private