ConcurrentSkipListMap 源码分析
ConcurrentSkipListMap
ConcurrentSkipListMap 能解决什么问题?什么时候使用 ConcurrentSkipListMap?
1)ConcurrentSkipListMap 是基于跳表实现的并发排序哈希表,映射可以根据键的自然顺序进行排序,
也可以根据创建映射时所提供的 Comparator 进行排序,具体取决于使用的构造方法。
2)ConcurrentSkipListMap 的 containsKey、get、put、remove 操作及其变体提供预期平均 log(n) 时间开销。
3)ConcurrentSkipListMap 的功能类似于 TreeMap 的线程安全版本。
4)ConcurrentSkipListMap 提供排序功能的同时,由于其插入、删除时需要维护跳表结构,性能低于 ConcurrentHashMap。
如何使用 ConcurrentSkipListMap?
1)需要使用 ConcurrentNavigableMap 的一些特性时使用 ConcurrentSkipListMap。
使用 ConcurrentSkipListMap 有什么风险?
1)ConcurrentSkipListMap 由于底层是通过跳跃表实现的,比较耗内存。
2)新增、删除元素时需要维护跳表结构,存在一定的性能损耗。
ConcurrentSkipListMap 核心操作的实现原理?
创建实例
/**
* 执行键排序的比较器,如果以自然顺序排序则为 null
*/
final Comparator<? super K> comparator;
/** 延迟初始化的顶层索引 */
private transient Index<K,V> head;
/** 延迟初始化的元素计数 */
private transient LongAdder adder;
/** 延迟初始化的键集合 */
private transient KeySet<K,V> keySet;
/** 延迟初始化的值集合 */
private transient Values<K,V> values;
/** 延迟初始化的映射集合 */
private transient EntrySet<K,V> entrySet;
/** 延迟初始化的降序映射 */
private transient SubMap<K,V> descendingMap;
/**
* 头结点和标记节点的 key 为 null,在节点被删除时将 val 置为null
*/
static final class Node<K,V> {
final K key; // currently, never detached
V val;
Node<K,V> next;
Node(K key, V value, Node<K,V> next) {
this.key = key;
this.val = value;
this.next = next;
}
}
/**
* 索引节点以表示跳表的层级
*/
static final class Index<K,V> {
// 驻留节点值
final Node<K,V> node; // currently, never detached
// 下节点
final Index<K,V> down;
// 右节点
Index<K,V> right;
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
this.node = node;
this.down = down;
this.right = right;
}
}
/**
* 创建一个使用自然顺序进行排序的空 ConcurrentSkipListMap 实例
*/
public ConcurrentSkipListMap() {
this.comparator = null;
}
/**
* 创建一个使用比较器 comparator 进行排序的空 ConcurrentSkipListMap 实例
*/
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
添加元素
/**
* 1)如果目标 key 不存在,则写入新的键值对,并返回 null。
* 2)如果目标 key 存在,则替换其关联的值,并返回旧值。
*/
@Override
public V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
if (key == null) {
throw new NullPointerException();
}
final Comparator<? super K> cmp = comparator;
for (;;) {
/**
* h:head 头结点
* b:predecessor 前置节点
*/
Index<K,V> h; Node<K,V> b;
VarHandle.acquireFence();
// 节点所在的层次
int levels = 0;
// 1)head==null 表示第一次插入元素
if ((h = head) == null) { // try to initialize
// 创建一个标记节点
final Node<K,V> base = new Node<>(null, null, null);
// 创建索引节点
h = new Index<>(base, null, null);
// 更新头节点
b = ConcurrentSkipListMap.HEAD.compareAndSet(this, null, h) ? base : null;
}
// 2)跳表已经有元素存在
else {
/**
* q:index node
* r:right node
* d:down node
*/
for (Index<K,V> q = h, r, d;;) { // count while descending
// 索引节点的右节点不为 null
while ((r = q.right) != null) {
Node<K,V> p; K k;
/**
* 右索引节点的驻留节点为 null ||
* 节点的键为 null ||
* 节点的值为 null
*/
if ((p = r.node) == null || (k = p.key) == null ||
p.val == null) {
// 删除接地啊 q 的右侧节点
ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
// 2)查找键大于当前节点键,则继续往右侧查找
} else if (ConcurrentSkipListMap.cpr(cmp, key, k) > 0) {
q = r;
// 3)查找键小于等于当前键,则当层节点已经查找完毕,往下层查找
} else {
break;
}
}
// 1)当前索引节点存在下层节点,则往下层查找
if ((d = q.down) != null) {
// 递增层级
++levels;
// 读取下层节点,重新进入循环并尝试往右侧查找
q = d;
}
else {
/**
* 2)已经到达最后一层
* 则读取驻留其上的节点值,开始玩右侧遍历
*/
b = q.node;
break;
}
}
}
// 前置节点存在
if (b != null) {
Node<K,V> z = null; // new node, if inserted
for (;;) {
/**
* n:node
* k:key
* v:value
* c:Comparisons 比较值
*/
Node<K,V> n, p; K k; V v; int c;
// 1)前置节点的 next 为 null,则表示当前节点是最后一个节点
if ((n = b.next) == null) {
if (b.key == null) {
ConcurrentSkipListMap.cpr(cmp, key, key);
}
c = -1;
}
// 2)节点键为 null
else if ((k = n.key) == null) {
break; // can't append; restart
// 3)节点值为 null,该节点已经被删除,需要从链表中踢除
} else if ((v = n.val) == null) {
ConcurrentSkipListMap.unlinkNode(b, n);
c = 1;
}
// 4)比较查找键和当前键,并且查找键比较大
else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
// 查找下一个节点
b = n;
/**
* 5)如果键相等 && 只有不存在才插入元素,则直接返回;否则尝试更新节点值
*/
} else if (c == 0 &&
(onlyIfAbsent || ConcurrentSkipListMap.VAL.compareAndSet(n, v, value))) {
// 更新成功则返回旧值
return v;
}
/**
* 目标键大于 predecessor.key 小于 predecessor.next.key,
* 将目标键值对插入到他们中间。
*/
if (c < 0 &&
ConcurrentSkipListMap.NEXT.compareAndSet(b, n,
p = new Node<>(key, value, n))) {
// 读取新增节点
z = p;
break;
}
}
if (z != null) {
// 读取随机数
final int lr = ThreadLocalRandom.nextSecondarySeed();
// 有 1/4 的机会基于新增节点生成索引节点
if ((lr & 0x3) == 0) { // add indices with 1/4 prob
final int hr = ThreadLocalRandom.nextSecondarySeed();
long rnd = (long)hr << 32 | lr & 0xffffffffL;
// 新增节点所在的层级,层级从 0 开始
int skips = levels; // levels to descend before add
Index<K,V> x = null;
for (;;) { // create at most 62 indices
// 基于新增节点生成顶层索引节点
x = new Index<>(z, x, null);
if (rnd >= 0L || --skips < 0) {
break;
} else {
rnd <<= 1;
}
}
/**
* 新增索引节点成功 &&
* 如果是新增顶层索引节点 &&
* 增加新的一层
*/
if (ConcurrentSkipListMap.addIndices(h, skips, x, cmp) && skips < 0 &&
head == h) { // try to add new level
// 将新增节点加到顶层
final Index<K,V> hx = new Index<>(z, x, null);
// 创建新的头节点
final Index<K,V> nh = new Index<>(h.node, h, hx);
// 更新头节点
ConcurrentSkipListMap.HEAD.compareAndSet(this, h, nh);
}
if (z.val == null)
{
findPredecessor(key, cmp); // clean
}
}
// 增加计数值
addCount(1L);
return null;
}
}
}
}
/**
* 在插入元素之后新增索引节点,从高层向低层递归插入,之后建立和前置节点的链接
* Recursion depths are exponentially less probable.
*
* @param q 当前层级的起始索引
* @param skips 插入索引时,需要跳过的层级数
* @param x 插入的目标索引
* @param cmp comparator
*/
static <K,V> boolean addIndices(Index<K,V> q, int skips, Index<K,V> x,
Comparator<? super K> cmp) {
Node<K,V> z; K key;
/**
* 1)新增索引节点不为 null &&
* 2)驻留数据节点不为 null &&
* 3)数据节点的键不为 null &&
* 4)起始索引节点不为 null
*/
if (x != null && (z = x.node) != null && (key = z.key) != null &&
q != null) { // hoist checks
boolean retrying = false;
for (;;) { // find splice point
/**
* r:right
* d:down
* c:Comparisons
*/
Index<K,V> r, d; int c;
/**
* 当前节点的右侧节点不为 null
*/
if ((r = q.right) != null) {
Node<K,V> p; K k;
// 1)尝试删除索引节点
if ((p = r.node) == null || (k = p.key) == null ||
p.val == null) {
ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
c = 0;
}
// 2)目标键比当前节点键大
else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
// 往右侧查找
q = r;
// 3)目标键和当前键相等
} else if (c == 0) {
break; // stale
}
// 4)目标键比当前节点键小?
} else {
// 已经不存在右侧节点
c = -1;
}
if (c < 0) {
// 下节点不为 null && 层级数 > 0
if ((d = q.down) != null && skips > 0) {
// 递减层级后,往下查找
--skips;
q = d;
}
/**
* 1)下节点不为 null && skip <=0 && 未出现索引添加失败 &&
* 尝试在当前层级添加索引
*/
else if (d != null && !retrying &&
!ConcurrentSkipListMap.addIndices(d, 0, x.down, cmp)) {
// 索引添加失败则退出
break;
} else {
x.right = r;
// 插入新增索引节点
if (ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, x)) {
// 执行成功则退出
return true;
}
else {
retrying = true; // re-find splice point
}
}
}
}
}
return false;
}
读取元素
/**
* 返回指定 key 映射的值,如果 key 不存在,则返回 null
*/
@Override
public V get(Object key) {
return doGet(key);
}
/**
* Gets value for key. Same idea as findNode, except skips over
* deletions and markers, and returns first encountered value to
* avoid possibly inconsistent rereads.
*/
private V doGet(Object key) {
Index<K,V> q;
VarHandle.acquireFence();
if (key == null) {
throw new NullPointerException();
}
final Comparator<? super K> cmp = comparator;
V result = null;
if ((q = head) != null) {
outer: for (Index<K,V> r, d;;) {
/**
* 首先向右侧查找,之后向下查找
*/
while ((r = q.right) != null) {
Node<K,V> p; K k; V v; int c;
// 节点已经被删除
if ((p = r.node) == null || (k = p.key) == null ||
(v = p.val) == null) {
ConcurrentSkipListMap.RIGHT.compareAndSet(q, r, r.right);
// 目标键 > 节点键,则向右侧查找
} else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
q = r;
// 找到目标键
} else if (c == 0) {
// 读取值后返回
result = v;
break outer;
} else {
break;
}
}
// 尝试向下层查找
if ((d = q.down) != null) {
q = d;
} else {
// 已经到达底层
Node<K,V> b, n;
// 读取索引节点驻留的数据节点之后,往右侧遍历查找
if ((b = q.node) != null) {
while ((n = b.next) != null) {
V v; int c;
final K k = n.key;
// 跳过被删除节点和标记节点,如果目标键 > 节点键,也向右侧查找
if ((v = n.val) == null || k == null ||
(c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
b = n;
// 目标键 <= 节点键
} else {
// 如果相等,则直接返回其值
if (c == 0) {
result = v;
}
// 不存在相等的键,则返回 null
break;
}
}
}
break;
}
}
}
return result;
}
/**
* 返回目标 key 关联的值,如果键不存在,则返回 defaultValue
*/
@Override
public V getOrDefault(Object key, V defaultValue) {
V v;
return (v = doGet(key)) == null ? defaultValue : v;
}
替换值
/**
* 如果目标 key 存在,则替换其值为 value,并返回旧值;否则返回 null。
*/
@Override
public V replace(K key, V value) {
// 键和值都非 null
if (key == null || value == null) {
throw new NullPointerException();
}
for (;;) {
Node<K,V> n; V v;
// 没找到目标键
if ((n = findNode(key)) == null) {
// 则返回 null
return null;
}
// 尝试原子设置值,并返回旧值
if ((v = n.val) != null && ConcurrentSkipListMap.VAL.compareAndSet(n, v, value)) {
return v;
}
}
}
/**
* 返回指定 key 映射的节点,如果键不存在,则返回 null。
* 查找过程中会踢除已经被删除键值对的索引节点,并且重新通过 findPredecessor 方法
* 查找基础节点进行再次遍历【如果节点的 key 为 null,则表示它是一个标记节点,
* 它的前置节点将被删除。】
* The traversal loops in doPut, doRemove, and findNear all include the same checks.
*/
private Node<K,V> findNode(Object key) {
if (key == null)
{
throw new NullPointerException(); // don't postpone errors
}
final Comparator<? super K> cmp = comparator;
Node<K,V> b;
// 读取底层有效的前置节点
outer: while ((b = findPredecessor(key, cmp)) != null) {
for (;;) {
Node<K,V> n; K k; V v; int c;
// 1)已经到达尾部,则直接退出
if ((n = b.next) == null) {
break outer; // empty
// 2)当前节点是标记节点
} else if ((k = n.key) == null) {
break; // b is deleted
// 3)当前索引节点驻留的数据节点关联的键值对已经被删除,则踢除当前节点
} else if ((v = n.val) == null) {
ConcurrentSkipListMap.unlinkNode(b, n); // n is deleted
// 4)目标键 > 节点键,则往右侧查找
} else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
b = n;
// 5)找到目标节点,则直接返回
} else if (c == 0) {
return n;
} else {
break outer;
}
}
}
return null;
}
/**
* 删除节点 n
*/
static <K,V> void unlinkNode(Node<K,V> b, Node<K,V> n) {
if (b != null && n != null) {
Node<K,V> f, p;
for (;;) {
if ((f = n.next) != null && f.key == null) {
p = f.next; // already marked
break;
}
// 被删除节点是尾节点
else if (ConcurrentSkipListMap.NEXT.compareAndSet(n, f,
new Node<>(null, null, f))) {
p = f; // add marker
break;
}
}
ConcurrentSkipListMap.NEXT.compareAndSet(b, n, p);
}
}
/**
* 如果目标 key 存在 && 旧值为 oldValue,则替换其值为 value,替换成功返回 true
*/
@Override
public boolean replace(K key, V oldValue, V newValue) {
if (key == null || oldValue == null || newValue == null) {
throw new NullPointerException();
}
for (;;) {
Node<K,V> n; V v;
// 节点不存在
if ((n = findNode(key)) == null) {
return false;
}
if ((v = n.val) != null) {
// 节点值和 oldValue 不相等
if (!oldValue.equals(v)) {
return false;
}
// 原子更新旧值
if (ConcurrentSkipListMap.VAL.compareAndSet(n, v, newValue)) {
return true;
}
}
}
}
删除键值对
/**
* 如果指定的 key 存在,则删除键值对,并返回旧值;否则返回 null
*/
@Override
public V remove(Object key) {
return doRemove(key, null);
}
/**
* 定位节点,将其值置为 null,在其后添加一个删除标记节点,断开前置节点,
* 移除关联的索引节点,并尝试递减层级。
*/
final V doRemove(Object key, Object value) {
if (key == null) {
throw new NullPointerException();
}
final Comparator<? super K> cmp = comparator;
V result = null;
Node<K,V> b;
outer: while ((b = findPredecessor(key, cmp)) != null &&
result == null) {
for (;;) {
Node<K,V> n; K k; V v; int c;
// 1)无后继节点
if ((n = b.next) == null) {
break outer;
// 2)当前节点是一个删除标记节点
} else if ((k = n.key) == null) {
break;
// 3)当前节点的数据节点被删除,则将其踢除
} else if ((v = n.val) == null) {
ConcurrentSkipListMap.unlinkNode(b, n);
// 4)目标键 > 节点键,则往右侧查找
} else if ((c = ConcurrentSkipListMap.cpr(cmp, key, k)) > 0) {
b = n;
// 5)无匹配键,则直接返回
} else if (c < 0) {
break outer;
// 6)如果值匹配,则删除的场景
} else if (value != null && !value.equals(v)) {
break outer;
// 7)将节点值置为 null
} else if (ConcurrentSkipListMap.VAL.compareAndSet(n, v, null)) {
// 读取旧值
result = v;
// 踢除节点
ConcurrentSkipListMap.unlinkNode(b, n);
break; // loop to clean up
}
}
}
if (result != null) {
// 尝试递减层级
tryReduceLevel();
addCount(-1L);
}
return result;
}
/**
* 如果指定的 key 存在 && 键关联的值和 value 相等,则删除键值对,并返回 true。
*/
@Override
public boolean remove(Object key, Object value) {
if (key == null) {
throw new NullPointerException();
}
return value != null && doRemove(key, value) != null;
}
ConcurrentSkipListMap 源码分析的更多相关文章
- 【死磕 Java 集合】— ConcurrentSkipListMap源码分析
转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...
- 死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug
前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级 ...
- 【JUC】JDK1.8源码分析之ConcurrentSkipListMap(二)
一.前言 最近在做项目的同时也在修复之前项目的一些Bug,所以忙得没有时间看源代码,今天都完成得差不多了,所以又开始源码分析之路,也着笔记录下ConcurrentSkipListMap的源码的分析过程 ...
- 【JUC】JDK1.8源码分析之ConcurrentSkipListSet(八)
一.前言 分析完了CopyOnWriteArraySet后,继续分析Set集合在JUC框架下的另一个集合,ConcurrentSkipListSet,ConcurrentSkipListSet一个基于 ...
- 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总
问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 并发-ConcurrentHashMap源码分析
ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...
- 4. SOFAJRaft源码分析— RheaKV初始化做了什么?
前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(fin ...
- JUC源码分析-集合篇:并发类容器介绍
JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...
随机推荐
- 断开ssh链接在后台继续运行命令
转载:http://blog.csdn.net/v1v1wang/article/details/6855552 对于linux运维,我们都是使用ssh登录到服务器,如果我们运行的任务需要很长时间或不 ...
- HNUSTOJ-1009 格雷码
1009: 格雷码 时间限制: 1 Sec 内存限制: 128 MB提交: 90 解决: 78[提交][状态][讨论版] 题目描述 对于给定的正整数n,格雷码为满足如下条件的一个编码序列:(1) ...
- 高性能迷你React框架anujs1.1.3发布
anujs现在只差一个组件(mention)就完全支持阿里的antd UI库了.一共跑通346个测试, 应该是全世界最接近官方React的迷你框架了. 以后的工作就是把React16的一些新特性支持了 ...
- django的模板的继承与导入
1.模板继承 母版中需要继承的地方: {% block content %} {% endblock %} 对应的子版中文件最开头写: {% extends 'head_demo.html' %} 然 ...
- lilybbs-faq - linux入门以及百合 Linux 版精华区导读
QUESTIONS 问题与解答 不需要任何命令的简单介绍 历史 (APUE 提到了1990年之前的unix历史,还有各种标准 ANSI ISO IEEE posix xpg3.... 但是linux ...
- Python核心技术与实战——六|异常处理
和其他语言一样,Python中的异常处理是很重要的机制和代码规范. 一.错误与异常 通常来说程序中的错误分为两种,一种是语法错误,另一种是异常.首先要了解错误和异常的区别和联系. 语法错误比较容易理解 ...
- u-boot 用哪个lds链接脚本
顶层Makefile文件中 : ifndef LDSCRIPT #LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug ifd ...
- Eclipse开发工具的编码问题
乱码:文件有一个编码,打开文件的工具(Eclipse或者浏览器)有一个编码,当两个编码不同就会出现编码异常或乱码. 参考: Eclipse修改编码格式 背景:在Eclipse的开发使用中,我们经常使用 ...
- Spring AOP注解配置demo
https://blog.csdn.net/yhl_jxy/article/details/78815636#commentBox
- 怎么在html动态实现显示和隐藏效果
效果目标图: 这个还是比较好实现的,附源码: <!DOCTYPE html> <html> <head> <meta charset="utf-8& ...