一、概述
JUC中比较有代表性的并发容器
二、ConcurrentHashMap
高并发情况下可以用的Map,HashMap不是线程安全的。HashTable,即线程安全又拥有Map的能力,但是他的实现是给每一个方法加上synchronized锁,效率低。所以可以选择concurrentHashMap。
1.7和1.8的区别:
jdk 1.7中是基于segment数组然后通过ReentrantLock对每一个segment元素加锁,来保证每一个segment内线程操作的安全性,从而保证了所有线程的操作安全;其中:默认的segment数组长度是16,也就是说concurrenthashmap1.7默认的并发度是16;
1.8中直接取消segment数组,采用和hashmap相同的结构 :Node数组+链表+红黑树;每一次增加操作是锁住一个Node元素,相对于1.7中是锁住一个segment,1.8的锁粒度要小一些(因为segment中是hashentry数组);引入红黑树的原因与hashmap的原因一样,当每一个Node下面有很多元素,链表过长的时候,访问的效率会变低,引入红黑树来使其查询操作的复杂度为logN
源码:
put(key,value)
-
如果table数组为null或者长度为0,则初始化一个长度为16的空的Node数组
-
如果目标位置为空,直接cas赋值
-
如果遇到扩容(即即将插入位置的元素hash值为-1),调用helpTransfer(tab,f)一起扩容。
-
如果存在哈希冲突,往链表或者红黑树里插入元素,链表长度大于9,判断数组长度大于64转成红黑树
如何统计数组的长度:
ConcurrentHashMap⾥总的kv数量就是:【baseCount数量】+ 【sum(CounterCells⾥所有CounterCell的value)】
basecount只有在多线程都进行相加,唯独只有一个线程抢占到了cas成功的才进行baseCount+,其他抢占失败的都会进入到countercells中进行相加。
三、ConcurrentLinkedQueue
非阻塞队列,使用循环CAS算法实现。是一个基于链接节点的无界线程安全的队列,按照先进先出原则对元素进行排序。新元素从队列尾部插入,而获取队列元素,则需要从队列头部获取。
构造函数创建了一个空节点作为构造函数第一个Node节点,底层由单向链表组成。高并发环境中性能最好的队列。入列出列线程安全,遍历不安全。不允许添加null元素
add:对tail的更新是会产⽣滞后的,也就是每次更新都会跳跃两个元素。这 么做的⽬的,就是为了减少cas操作的次数
remove:
哨兵:
四、CopyOnWriteArrayList
读操作不加锁,写操作也不会阻塞读,写入操作时,进行一次自我复制,产生一个副本,写操作就在副本中执行,写完之后,再将副本替换原来的数据。
-
get(int index):从数组中获取对应下标的元素,因为读操作不需要加锁,所以get方法就是一个普通的不加锁的方法。
-
set(E e):执行写操作时,首先进行lock加锁,然后复制原数组,创建一个长度+1的新数组,在新数组执行新增操作。完毕后,将新数组替换旧数组。由于数组时volatile的,所以替换之后,多线程之间是可见的。
五、BlockingQueue阻塞队列
BlockingQueue 不接受 null 元素。试图 add 、 put 或 offer ⼀个 null 元素 时,某些实现会抛出 NullPointerException 。 null 被⽤作指示 poll 操作失败的警戒值。
阻塞队列常用的方法
offer()如果队列满了无法插入,立即返回false;poll()如果队列是空的无法删除,立即返回false。
更关注put()take()操作失败,阻塞是怎样实现的。
1.ArrayBlockingQueue
构造函数,默认采用非公平锁,底层使用对象数组保存元素
添加图片注释,不超过 140 字(可选)
put时,如果队列满了,notFull.await();notEmpty.signal()
take时,如果队列空了,notEmpty.await();notFull.signal()
2.LinkedBlockingQueue
构造函数默认长度,2^31,底层使用链表维护元素,在构造函数中,创建一个空的节点,作为整个链表的头节点。
put时,count.get()为capacity,notFull.await();执行完入队操作,如果队列未满,唤醒其他put导致阻塞的线程;c==0时,表明队列已经有元素,signalNotEmpty()
take时,count.get()为0,notEnpty.await();执行完出队操作,队列中仍有节点,signalNotFull()