ArrayBlockingQueue:底层是循环数组,不涉及扩容
LinkedBlokingQueue:底层是单向链表,头尾两把锁,所以put和put互斥,take和take互斥,put和take不互斥,有一个原子类型的count变量
ProiorityBlockingQueue:优先级从小到大队列,里面元素实现Comparable接口,基于数组,初始大小11,会扩容,内部原理是维护了一个最小二叉堆
DelayQueue:按照延迟时间从小到大的ProiorityBlockingQueue
SynchronousQueue :本身没有容量,只能允许一个元素,一个线程put会锁定,只有另一个线程调用take才解锁。有公平模式和非公平模式。公平就是第一个put线程会在队列头,和第一个take配对。非公平就是随机。
BlockingDeque:双端队列,底层是双向链表。
CopyOnWrite:写的时候不是直接写数据源,而是把数据拷贝出来写副本,再通过乐观锁等方式写回,这么做的好处是为了读不加锁。
CopyOnWriteArrayList:底层是数组,写的时候写副本
CopyOnWriteArraySet:对CopyOnWriteArrayList进行再封装,保证元素不重复
ConCurrentLinkedQueue:单向链表,线程安全,基于CAS,
ConCurrentLinkedDeque:
ConCurrentHashMap:
1.8实现:
数组+链表+红黑树(数组长度未超过64,只会扩容,不会把链表转化成红黑树),线程安全,对每一个node加锁,多少个node就支持多少个并发,初始长度16个node。
构造方法:
构造方法里设置一个sizeCtrl=数组长度,只初始化长度,并未实际的初始化数组,多个线程往里方元素的时候才进行初始化
初始化
多个线程的竞争是通过CAS写sizeCtrl进行的,某个线程把sizeCtrl设置成-1,就拥有初始化的权利,其他线程自选等待,初始化完成再把sizeCtrl设置回去,因为初始化过程比较简单,所以这里只用了单线程进行初始化。
put实现分析:
总共分4块:
1.整体数组初始化,前面说了
2.第i个元素初始化,所在的槽位为空,说明该元素是该槽位的第一个元素,直接放入头结点
3.扩容,帮助数组扩容
4.放入元素,把元素放入槽内,有可能是链表,有可能是红黑树,通过头结点类型判断,分支代码包裹在synchronized中,意味着每个数组元素有一把锁,并发度就等于数组元素个数
扩容
1.建一个新的hashmap,长度是原数组长度的2倍,把旧元素逐个迁移过来,每个线程负责一部分元素的迁移,需要的线程数是N/stride,N就是原数组长度,stride=(n>>>3)/ncpu。
2.用一个transferIndex来记录扩容进度,初始值为N,每次通过CAS减少stride个位置,最终减少至n<=0,说明扩容完成。
3.扩容完成前,有些槽已经移动到新map里的,有的还在旧的里,访问未迁移的不受影响,访问迁移完的会通过一个转发节点来负责转发到新的map里
4.因为原数组长度是2的整数次方,扩容又乘以2,hash函数又是hashcode%length,所以处在第i个位置的库容后依然处在第i位或者i+n位。也就是说把tab[i]位置的数组或者红黑树拆成2部分,一部分在tab[i],一部分在tab[i+n]
sizeCtrl=-1,初始化
sizeCtrl=其他负数,多个线程正在并发扩容
sizeCtrl=数组长度,表示未开始之前的初始容量
ConCurrentSkipListMap:线程安全的有序map,按key排序,基于跳跃表,因为可以无锁的增加删除节点
ConcurrentSkipListSet :对ConCurrentSkipListMap简单封装,通过key来排序
