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 源码分析的更多相关文章

  1. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  2. 死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug

    前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级 ...

  3. 【JUC】JDK1.8源码分析之ConcurrentSkipListMap(二)

    一.前言 最近在做项目的同时也在修复之前项目的一些Bug,所以忙得没有时间看源代码,今天都完成得差不多了,所以又开始源码分析之路,也着笔记录下ConcurrentSkipListMap的源码的分析过程 ...

  4. 【JUC】JDK1.8源码分析之ConcurrentSkipListSet(八)

    一.前言 分析完了CopyOnWriteArraySet后,继续分析Set集合在JUC框架下的另一个集合,ConcurrentSkipListSet,ConcurrentSkipListSet一个基于 ...

  5. 死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

    问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)Concurren ...

  6. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  7. 并发-ConcurrentHashMap源码分析

    ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...

  8. 4. SOFAJRaft源码分析— RheaKV初始化做了什么?

    前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(fin ...

  9. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

随机推荐

  1. 小白学Python(20)—— Turtle 海龟绘图

    Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行 ...

  2. Optional接口简记

    @Data public class Employee { private String name; } @Data public class Company { private String nam ...

  3. 浅析DES、AES、RSA、MD5加密算法及其应用场景

    对称加密算法DES 算法:一种典型的块加密方法,将固定长度的明文通过一系列复杂的操作变成同样长度的密文,块的长度为64位.同时,DES 使用的密钥来自定义变换过程,因此算法认为只有持有加密所用的密钥的 ...

  4. Socket通讯-C#客户端与Java服务端通讯(发送消息和文件)

    设计思路 使用websocket通信,客户端采用C#开发界面,服务端使用Java开发,最终实现Java服务端向C#客户端发送消息和文件,C#客户端实现语音广播的功能. Java服务端设计 packag ...

  5. PAT Basic 1036 跟奥巴马一起编程 (15 分)

    美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统.2014 年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个 ...

  6. PAT Advanced 1006 Sign In and Sign Out (25 分)

    At the beginning of every day, the first person who signs in the computer room will unlock the door, ...

  7. Codeforces 955 LR询问 多次幂处理

    A 模拟题 #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) ...

  8. python添加清屏功能

    创建文件ClearWindow添加内容 class ClearWindow: menudefs = [ ('options', [None, ('Clear Shell Window', '<& ...

  9. jenkins插件send files or execute commands over ssh插件parameterized publishing选项使用

    1.设置一个参数 2.设置label 3.勾选parameterized publishing

  10. SSM整合中错误:Data truncation: Data too long for column 'gender' at row 1

    错误描述 ### SQL: insert into t_customer(name,gender,phone,address) values (?,?,?,?) ### Cause: com.mysq ...