ConcurrentSkipListMap
1. 前言
- ConcurrentSkipListMap是线程安全的有序的Map,适用于高并发的场景。
- 不允许空键、值
- ConcurrentSkipListMap和TreeMap的区别:
- 都是有序的哈希表
- ConcurrentSkipListMap是线程安全的,而TreeMap是非线程安全的
- ConcurrentSkipListMap是通过跳表实现的,而TreeMap是通过红黑树实现的。
- 跳表(Skip List):它是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。
2. 源码分析
2.1 数据结构
UML类图:
跳表:
2.2 内部类
- Index
1 | static class Index<K,V> { |
- HeadIndex
1 | static final class HeadIndex<K,V> extends Index<K,V> { |
- Node
1 | static final class Node<K,V> { |
2.3 核心函数
2.3.1 put(K key, V value)
1 | public V put(K key, V value) { |
put()通过doPut()方法添加键值对:
1 | private V doPut(K key, V value, boolean onlyIfAbsent) { |
流程如下:
- 根据给定的key从跳表的左上方往右或者往下查找到Node链表的前驱Node结点,这个查找过程会删除一些已经标记为删除的结点
- 找到前驱结点后,开始往后插入查找插入的位置(因为找到前驱结点后,可能有另外一个线程在此前驱结点后插入了一个结点,所以先前查找的前驱现在可能不是要插入的结点的前驱,所以需要往后查找)。
- 随机生成一个种子,判断是否需要增加层级,并且在各层级中插入对应的Index结点。
2.3.2 remove()
1 | public V remove(Object key) { |
1 | final V doRemove(Object key, Object value) { |
流程如下:
- 根据key值找到前驱结点,查找的过程会删除一个标记为删除的结点
- 从前驱结点往后查找该结点
- 在该结点后面添加一个marker结点,若添加成功,则将该结点的前驱的后继设置为该结点之前的后继
- 头结点的next域是否为空,若为空,则减少层级。
1 | // 减少跳表层级 |
说明:如果最高的前三个HeadIndex不为空,并且其right域都为null,那么就将level减少1层,并将head设置为之前head的下一层,设置完成后,还有检测之前的head的right域是否为null,如果为null,则减少层级成功,否则再次将head设置为h。
2.3.4 get()
1 | public V get(Object key) { |
1 | private V doGet(Object key) { |