并发映射--ConcurrentMap
Java集合系列
ConcurrentMap
一.概述
ConcurrentMap及其子类是JDK1.5提供的一套用于应对高并发的映射机制,在高并发时能比较好地保证线程安全。
二. ConcurrentHashMap(并发哈希映射)
-
性质(其下前4点同HashMap的性质)
-
底层基于数组+链表的结构实现
-
数组的默认容量16(表示有16个桶)
-
默认加载因子:0.75
-
默认扩容方法:增加一倍的桶容量(13个桶的时候开始扩容,12只是个边界值,非扩容点)
-
若给定初始值,则会在底层有以下判断(源码)
-
其中
MAXIMUM_CAPACITY
=230 可以看出,ConcurrentHashMap最多允许存在230个桶。 -
若初始值 >=230-1 则给最大容量,否则按运算规则计算真实容量。经计算,若初始值给19,则实际容量为32 ;若初始值给48则实际容量为128。理论上可证,给出任意一个初始值,其真实大小一定是2的n次方。进一步可以发现:
-
注意:向下取整
\[ 19 \rightarrow 19+1+\lfloor19/2\rfloor=29 \rightarrow 16<29<32\rightarrow32\\ 48 \rightarrow 48+1+\lfloor48/2\rfloor=73 \rightarrow 64<73<128\rightarrow 128 \] -
-
与ConcurrentHashMap不同,HashMap直接拿值进入
tableSizeFor
计算,有兴趣可以看看源码。
🌞 问:若给定初始容量为100,在极端情况下,ConcurrentHashMap需不需要扩容?HashMap需不需要扩容?
【注】极端条件:元素都放在桶里
- ConcurrentHashMap:
\[100+1+\lfloor100\div2\rfloor=151\\ 128<151<256\\ 256\times0.75>100 \]=> 不需要扩容
- HashMap:
\[64<100<128\\ 128\times0.75<100 \]=> 需要扩容
-
-
在JDK1.8中,为了提高效率引入了红黑树机制,当桶中的元素个数超过8时,这个桶的链表扭转成一颗红黑树。如果红黑树的节点个数不足7个时,红黑树扭转回链表。
- 扭转成红黑树的必要前提:桶的数量>=64
🌙红黑树(本质上是一棵自平衡二叉查找树)
- 树:一种数据结构,每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树;
- 二叉树:每个结点最多只能有两棵子树,且有左右之分
- 二叉查找树(二叉排序树):
1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 左、右子树也分别为二叉排序树;
4. 没有键值相等的结点。 - 平衡二叉查找树:叶节点高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。(平衡的特点)
- 红黑树(自平衡二叉树):
- 所有节点非红即黑。
- 根节点为黑。
- 红节点的子节点一定为黑(反之不成立)。
- 叶子节点是黑色的空节点。
- 从根节点到叶子节点经过的路径中黑色节点个数一致,即黑节点高度一致。
- 新添节点的颜色一定是红的。(与第三点有可能相背,这时就需要修正了)
- 需要在代码上手动实现自平衡的特点(修正)
- 红黑树在构建过程中,每次添加一个新的节点都需要考虑是否需要修正:
- 涂色
- 左旋
- 右旋
- 时间复杂度:Olog(n)
- 异步线程安全
-
HashMap-异步线程不安全。因为没有锁,可能发生两个线程操作同一个链。
-
Hashtable-对外提供的所有方法都加了锁(除了构造方法),锁对象是this。因此,其会把整个映射都锁起来,虽然线程安全了,但是只能同步访问,效率低下。
-
ConcurrentHashMap-采用了分段(桶)锁机制,在后序版本中,ConcurrentHashMap在分段锁基础上引入了读写锁机制:
- 读锁:允许多个线程同时读,但是读的过程中不允许有写操作。
- 写锁:只允许一个线程写,不允许线程读。
-
CAS-ConcurrentHashMap利用锁保证线程安全,但是在使用锁的时候,造成CPU的资源浪费(例如线程调度,线程上下文切换等)在JDK1.8中,考虑到锁带来的开销,引入了无锁算法CAS(Compare And Swp)
-
官方解释:我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的实际值是多少。
其中:V代表内存值,A代表旧的预期值,B代表新的预期值。
其目的在于判断欲修改的值是否被修改过。
-
CAS的过程一旦被打断,就会重新开始。以确保操作的原子性。
-
因为CAS涉及到线程的重新调度问题,所以需要结合具体的cpu内核架构来涉及,因此Java的CAS底层是依靠C语言实现的。目前市面上,服务器流行的内核架构都是支持CAS。
-
-
三. ConcurrentNavigableMap(并发导航映射)
-
提供了用于截取子映射的方法
-
ConcurrentNavigableMap是一个接口,一般使用的是它的实现类:ConcurrentSkipListMap:并发跳跃映射表。底层基于跳跃表实现。
-
跳跃表:
- 针对有序列表进行操作。
- 适用于查询多而增删少的场景。
- 以空间换时间。(构建跳跃表)
- 跳跃表允许进行多层提取,最上层元素个数不能少于两个。
- 如果在跳跃表中新添了元素,在考虑是否提取到上层的跳跃表中遵循"抛硬币"原则。
- 时间复杂度:O(logn)
-
几个方法和性值