【JUC源码解析】ConcurrentSkipListMap
简介
基于跳表,支持并发,有序的哈希表。
跳表
红色路径为寻找结点F。
拿空间换时间,时间复杂度,O(nlogn).
源码分析
内部类
Node
属性
final K key; // 键
volatile Object value; // 值
volatile Node<K,V> next; // 指向下一个结点
最底层是基础层,即使结点(Node)层,保存实际数据(value),next指向下一个结点
构造方法
Node(K key, Object value, Node<K,V> next) { // 构造方法
this.key = key;
this.value = value;
this.next = next;
} Node(Node<K,V> next) { // 构造方法,用来构建标记结点,特点是值为自身
this.key = null;
this.value = this;
this.next = next;
}
标记(marker)结点的value值是自身。
核心方法
boolean isBaseHeader() { // 是否为头结点(每级)
return value == BASE_HEADER;
} boolean appendMarker(Node<K,V> f) { // 插入标记结点
return casNext(f, new Node<K,V>(f));
} void helpDelete(Node<K,V> b, Node<K,V> f) { // 帮助删除
if (f == next && this == b.next) { // 如果b和f分别是自己的前驱结点和后继结点
if (f == null || f.value != f) // 当前结点还没有被标记删除(后接标记结点)
casNext(f, new Node<K,V>(f)); // 直接删除当前结点
else // 如果已经标记为删除,则一次性删除当前结点后标记结点
b.casNext(this, f.next);
}
}
Index
属性
final Node<K,V> node; // 指向实际结点
final Index<K,V> down; // 指向下级索引
volatile Index<K,V> right; // 指向右侧索引
自第一层往上,是索引层,各层的node域均指向垂直向下的结点(Node),down指向下面一层的索引,right指向右边一个索引
构造方法
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) { // 构造方法
this.node = node;
this.down = down;
this.right = right;
}
核心方法
final boolean indexesDeletedNode() { // 删除索引结点
return node.value == null;
} final boolean link(Index<K,V> succ, Index<K,V> newSucc) { // 链接(右侧插入)新索引
Node<K,V> n = node; // 当前索引的node域
newSucc.right = succ; // 新索引的right域名设为当前索引的右侧索引
return n.value != null && casRight(succ, newSucc); // 若node结点没被删除,设置新索引到当前索引的right域
} final boolean unlink(Index<K,V> succ) { // 解除当前索引的右侧索引
return node.value != null && casRight(succ, succ.right); // 若node结点没被删除,设置右侧索引的右侧索引到当前索引的right域
}
HeadIndex
static final class HeadIndex<K,V> extends Index<K,V> { // 继承Index
final int level; // 级别
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}
索引层最左边的索引,记录级别(层数)
数据结构
横向看,最下面一层是基础层,即是结点层,从第一层开始往上是索引层 ,head指向顶层最左边的索引。
纵向看,最左边一列是层数,0层(虚构的)是结点层,第二列第一行往上是头索引,右上是索引结点。
每个索引结点都有right域,指向右边的索引,也有down域,指向下面的索引,第一层索引的down域指向null,所有索引的node域均指向其下方的Node结点
基础层(结点层)有next域,指向右边的结点。
头索引有level属性,记录层级。
属性
private static final Object BASE_HEADER = new Object(); // 标明基础层(Node层)头节点 private transient volatile HeadIndex<K,V> head; // 最顶层头节点 final Comparator<? super K> comparator; // 比较器 private transient KeySet<K> keySet; // 键集合 private transient EntrySet<K,V> entrySet; // 键值对集合 private transient Values<V> values; // 值集合 private transient ConcurrentNavigableMap<K,V> descendingMap; // 降序(键)集合
构造方法
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
} public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
} public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
initialize();
putAll(m); // 将m中的元素加入跳表
} public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.comparator = m.comparator();
initialize();
buildFromSorted(m); // 根据m的元素顺序批量构建跳表
}
核心方法
initialize()
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null), null, null, 1); // Node.value = BASE_HEADER, 基础层(Node层)头节点
}
doPut(K, V, boolean)
方法签名
private V doPut(K key, V value, boolean onlyIfAbsent) { }
寻找插入点,构建新结点,完成基础层(Node层)的插入操作
Node<K, V> z; // 指向待插入结点
if (key == null) // 参数校验,键为空,抛出空指针异常
throw new NullPointerException();
Comparator<? super K> cmp = comparator; // 比较器
outer: for (;;) { // 外层循环
for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // n为当前结点,b是n的前驱结点,新结点是要插入到b和n之间的
if (n != null) {
Object v; // 指向当前结点的value
int c; // 比较key的结果
Node<K, V> f = n.next; // n的后继结点
if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
break; // 重新读取
if ((v = n.value) == null) { // 如果n被删除
n.helpDelete(b, f); // 去帮助删除,使其尽快结束
break; // 重新读取
}
if (b.value == null || v == n) // b结点被删除(其value为null,或其后继结点是marker结点)
break;
if ((c = cpr(cmp, key, n.key)) > 0) { // key大于n结点的key值,因为要插入到当前结点的前面,所以不满足要求,需要右移
b = n;
n = f; // 右移
continue; // 继续
}
if (c == 0) { // 键相等,替换
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked")
V vv = (V) v;
return vv; // 返回旧值
}
break; // CAS失败,有别的线程捣乱,重来
}
} z = new Node<K, V>(key, value, n); // 构建新结点
if (!b.casNext(n, z))
break; // CAS失败,有别的线程捣乱,重来
break outer; // 成功,则跳出外层循环
}
}
随机选取层(大于当前最大层,新增一层,同时更新顶层头索引head),构建纵向索引链
int rnd = ThreadLocalRandom.nextSecondarySeed(); // 获取一个线程无关的随机数,int类型,32位
if ((rnd & 0x80000001) == 0) { // 最高位和最低位为1的情况下,除基础层新增结点外,各层均不再增索引
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0) // 低位(从第2位开始)连续为1的个数,作为选取层
++level;
Index<K, V> idx = null; // 指向从顶层开始第一个需要调整的索引,一般是选择层level,如果level大于max,说明需要新增一层,而新增的那层(level层)不需要调整(head->HeadIndex)指向它就行,所以此时,idx指向level-1层新增的索引
HeadIndex<K, V> h = head; // 顶层头索引
if (level <= (max = h.level)) { // 如果选取的层没有超出最大层
for (int i = 1; i <= level; ++i) //构建一个从 1 层到 level层的纵纵向索引(Index)链
idx = new Index<K, V>(z, idx, null); // 此时,idx指向level层新增的索引
} else { // 如果选取层超过了最大层,则增加一层索引
level = max + 1; // 此时,level为老的最大层加1
@SuppressWarnings("unchecked")
Index<K, V>[] idxs = (Index<K, V>[]) new Index<?, ?>[level + 1]; // 用长度为level+1的数组,保存各层(1到level层,数组[0]未使用,为的是索引的层数与其所在数组的下标相等)新增索引的引用
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K, V>(z, idx, null); // 每层新增索引保存在对应其层数的下标位置处,idx最后指向level层新增的索引
for (;;) {
h = head; // 再次获取顶层头索引
int oldLevel = h.level; // 获取老的最大层
if (level <= oldLevel) // 如果选取层又比oldLevel小了,说明,别的线程抢先更新过跳表了
break; // 跳出循环,idx最后指向level层的索引,同没有超出最大层的情况
HeadIndex<K, V> newh = h;
Node<K, V> oldbase = h.node;
for (int j = oldLevel + 1; j <= level; ++j) // 更新新增层纵向头索引,正常情况下只新增一层
newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j); // newh最后指向顶层头索引
if (casHead(h, newh)) { // CAS head(head始终指向顶层头索引)
h = newh; // h也指向顶层头索引
idx = idxs[level = oldLevel]; // idx指向老的顶层头索引
break; // 跳出循环
}
}
}
自选取层至底层,连接各层新增索引,完成跳表的构建
splice: for (int insertionLevel = level;;) {
int j = h.level;
for (Index<K, V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null) // 头节点被删除,或者新增索引为空,直接跳出外层循环
break splice;
if (r != null) {
Node<K, V> n = r.node; // 获得右索引结点
int c = cpr(cmp, key, n.key); // 比较key
if (n.value == null) { // n(正在)被删除
if (!q.unlink(r)) // 解除r索引
break; // 如果失败,说明有别的线程干预,跳出内循环,重新获取level
r = q.right; // 获取新的右索引
continue; // 继续
}
if (c > 0) { // key大于n结点的key值,需要右移
q = r;
r = r.right; // 右移
continue; // 继续
}
} if (j == insertionLevel) {
if (!q.link(r, t)) // 将t插在q和r之间
break; // 如果失败,跳出内循环,重试
if (t.node.value == null) { // t索引指向的结点被删除
findNode(key); // 清理删除的结点
break splice; // 跳出外层循环
}
if (--insertionLevel == 0) // 处理结束
break splice; // 跳出外层循环
} if (--j >= insertionLevel && j < level)
t = t.down; // 处理下一层索引
q = q.down; // 同步更新
r = q.right; // 同步更新
}
}
doRemove(Object, Object)
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // 寻找key的前驱结点,若key在跳表中,结点n即是key在跳表的结点
Object v;
int c;
if (n == null) // n被删除,直接跳出外层循环
break outer;
Node<K, V> f = n.next; // n的后继结点
if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
break; // 重新读取
if ((v = n.value) == null) { // n被删除
n.helpDelete(b, f); // 帮助删除
break; // 重新检查
}
if (b.value == null || v == n) // b被删除(其value为null,或其后继结点是marker结点)
break;
if ((c = cpr(cmp, key, n.key)) < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
break outer; // 直接退出外层循环
if (c > 0) { // 要找的结点还在n后面,右移
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v)) // 传入的value不等于结点的value值,说明别的线程已经修改过了,不予删除
break outer;
if (!n.casValue(v, null)) // CAS v -> null
break; // CAS失败,则重新来过
if (!n.appendMarker(f) || !b.casNext(n, f)) // 尝试在n结点后接marker结点,如果失败,则重试;若成功,则尝试CAS b结点的next域,失败,也重试
findNode(key); // 清理删除的结点
else {
findPredecessor(key, cmp); // 清理无用的索引
if (head.right == null)
tryReduceLevel(); // 并清除没有索引的层
}
@SuppressWarnings("unchecked")
V vv = (V) v;
return vv;
}
}
return null;
}
查找到要删除的结点后,首先CAS其value为null,失败重试,若成功后,继续在其后添加marker结点,接着CAS其前驱结点(b)的next为其后继结点(f),若都成功,则结束,否则, 调用findNode方法,该方法会遍历跳表,并帮助删除value为null的结点。
marker结点的作用是为了降低b结点的并发性,若是没有marker结点,那么所有的CAS压力全集中在了结点上,若是有marker结点,那么会首先对n结点(待删除结点)CAS,成功后,才去找b结点,否则,直接就调用findNode方法,以期清理待删除的结点。
doGet(Object)
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v;
int c;
if (n == null) // n被删除,直接跳出
break outer;
Node<K, V> f = n.next;
if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
break; // 重新读取
if ((v = n.value) == null) { // n正在被删除
n.helpDelete(b, f); // 帮助删除
break;
}
if (b.value == null || v == n) // b已经被删除
break;
if ((c = cpr(cmp, key, n.key)) == 0) { // 找到结点
@SuppressWarnings("unchecked")
V vv = (V) v;
return vv;
}
if (c < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
break outer;
b = n; // 右移,继续
n = f;
}
}
return null;
}
for (;;) {
for (;next;)
if(!cas){
break;
}
}
findFirst()
final Node<K, V> findFirst() {
for (Node<K, V> b, n;;) {
if ((n = (b = head.node).next) == null) // 头结点不是数据结点,所以要从第二个开始,如果为空,返回null
return null;
if (n.value != null) // 结点(第二个结点,第一个数据结点)没被删除,返回此结点
return n;
n.helpDelete(b, n.next); // 否则,清理已经删除结点,继续往后找
}
}
findPredecessor(Object, Comparator<? super K>)
private Node<K, V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException();
for (;;) {
for (Index<K, V> q = head, r = q.right, d;;) { // 自顶层头索引开始查找
if (r != null) {
Node<K, V> n = r.node;
K k = n.key;
if (n.value == null) { // 正在被删除
if (!q.unlink(r)) // 清除r索引
break; // 失败重来
r = q.right; // 重新读取r
continue; // 继续
}
if (cpr(cmp, key, k) > 0) { // key比结点的k大,需要往后查找
q = r;
r = r.right; // 右移
continue; // 继续
}
}
if ((d = q.down) == null) // 最底层索引,再往下是基础层(结点层)
return q.node;
q = d; // 继续右下查找
r = d.right;
}
}
}
查找前驱结点
findNode(Object)
private Node<K, V> findNode(Object key) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // b为key的前驱结点,n应该为key在跳表中的结点
Object v;
int c;
if (n == null) // n被删除,直接跳出
break outer;
Node<K, V> f = n.next;
if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
break; // 重新读取
if ((v = n.value) == null) { // n正在被删除
n.helpDelete(b, f); // 帮助删除
break; // 重新来过
}
if (b.value == null || v == n) // b已经被删除
break; // 重新来攻
if ((c = cpr(cmp, key, n.key)) == 0) // 找到结点
return n; // 返回
if (c < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
break outer; // 退出外层循环
b = n; // 右移,继续
n = f;
}
}
return null;
}
查找结点
tryReduceLevel()
private void tryReduceLevel() {
HeadIndex<K, V> h = head;
HeadIndex<K, V> d;
HeadIndex<K, V> e;
if (h.level > 3 // 层级大于3才考虑缩减层级
&& (d = (HeadIndex<K, V>) h.down) != null // h是顶层,d是自顶层起第二层
&& (e = (HeadIndex<K, V>) d.down) != null // e是第三层
&& e.right == null
&& d.right == null
&& h.right == null // h, d, e三层索引均为空
&& casHead(h, d) // 设置顶层头索引为d,即第二层
&& h.right != null) // 如果h层又有了索引
casHead(d, h); // 需要将顶层头索引再设置回来
}
从上往下连着三层为空,才尝试将顶层缩减掉,中途如果发现顶层又有索引了,还得把顶层加回来。
buildFromSorted(SortedMap<K, ? extends V>)
private void buildFromSorted(SortedMap<K, ? extends V> map) {
if (map == null)
throw new NullPointerException(); HeadIndex<K, V> h = head;
Node<K, V> basepred = h.node; ArrayList<Index<K, V>> preds = new ArrayList<Index<K, V>>(); // 保存各层的索引,每层最右边的索引 for (int i = 0; i <= h.level; ++i)
preds.add(null);
Index<K, V> q = h;
for (int i = h.level; i > 0; --i) { // 初始是各层头索引
preds.set(i, q);
q = q.down;
} Iterator<? extends Map.Entry<? extends K, ? extends V>> it = map.entrySet().iterator();
while (it.hasNext()) { // 根据传入的map的元素顺序依次添加
Map.Entry<? extends K, ? extends V> e = it.next();
int rnd = ThreadLocalRandom.current().nextInt();
int j = 0;
if ((rnd & 0x80000001) == 0) { // 同doPut方法
do {
++j;
} while (((rnd >>>= 1) & 1) != 0);
if (j > h.level)
j = h.level + 1;
}
K k = e.getKey();
V v = e.getValue();
if (k == null || v == null)
throw new NullPointerException();
Node<K, V> z = new Node<K, V>(k, v, null);
basepred.next = z;
basepred = z;
if (j > 0) {
Index<K, V> idx = null;
for (int i = 1; i <= j; ++i) {
idx = new Index<K, V>(z, idx, null);
if (i > h.level)
h = new HeadIndex<K, V>(h.node, h, idx, i); if (i < preds.size()) {
preds.get(i).right = idx; // 添加新索引
preds.set(i, idx); // 只保存最右边的索引
} else
preds.add(idx);
}
}
}
head = h;
}
根据传入的map的元素顺序,依次添加到跳表里,使用List保存各层索引(最右边),批量处理索引关系,由于只在构造方法或clone方法里调用,一来很难有并发问题,而来,初始时跳表应该为空,批量添加更合适。
行文至此结束。
尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_skip.html
【JUC源码解析】ConcurrentSkipListMap的更多相关文章
- 【JUC源码解析】ScheduledThreadPoolExecutor
简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...
- 【JUC源码解析】SynchronousQueue
简介 SynchronousQueue是一种特殊的阻塞队列,该队列没有容量. [存数据线程]到达队列后,若发现没有[取数据线程]在此等待,则[存数据线程]便入队等待,直到有[取数据线程]来取数据,并释 ...
- 【JUC源码解析】ForkJoinPool
简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...
- 【JUC源码解析】DelayQueue
简介 基于优先级队列,以过期时间作为排序的基准,剩余时间最少的元素排在队首.只有过期的元素才能出队,在此之前,线程等待. 源码解析 属性 private final transient Reentra ...
- 【JUC源码解析】CyclicBarrier
简介 CyclicBarrier,一个同步器,允许多个线程相互等待,直到达到一个公共屏障点. 概述 CyclicBarrier支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后 ...
- 【JUC源码解析】ConcurrentLinkedQueue
简介 ConcurrentLinkedQueue是一个基于链表结点的无界线程安全队列. 概述 队列顺序,为FIFO(first-in-first-out):队首元素,是当前排队时间最长的:队尾元素,当 ...
- 【JUC源码解析】Exchanger
简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...
- Jdk1.6 JUC源码解析(12)-ArrayBlockingQueue
功能简介: ArrayBlockingQueue是一种基于数组实现的有界的阻塞队列.队列中的元素遵循先入先出(FIFO)的规则.新元素插入到队列的尾部,从队列头部取出元素. 和普通队列有所不同,该队列 ...
- Jdk1.6 JUC源码解析(13)-LinkedBlockingQueue
功能简介: LinkedBlockingQueue是一种基于单向链表实现的有界的(可选的,不指定默认int最大值)阻塞队列.队列中的元素遵循先入先出 (FIFO)的规则.新元素插入到队列的尾部,从队列 ...
随机推荐
- BZOJ3566:[SHOI2014]概率充电器(树形DP,概率期望)
Description 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品——概率充电器: “采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI 概率充电器, ...
- bzoj 3339 Rmq Problem / mex
题目 我的树状数组怎么那么慢啊 就是一道水题,我们考虑一下对于一个区间\([l,r]\)什么样的数能被计算 显然需要对于一个\(j\),需要满足\(j<l\)且\(nxt_{j}>r\), ...
- 8、Web Service-IDEA-jaxws规范下的 spring整合CXF
前提:开发和之前eclipse的开发有很大的不同! 1.服务端的实现 1.新建项目 此时创建的是web项目 2.此时创建的项目是不完整的需要开发人员手动补充完整 3.对文件夹的设置(满满的软件使用方法 ...
- 一款不错的网站压力测试工具webbench
webbench最多可以模拟3万个并发连接去测试网站的负载能力,个人感觉要比Apache自带的ab压力测试工具好,安装使用也特别方便. 1.适用系统:Linux 2.编译安装: 引用 wget htt ...
- php memcache分布式和要注意的问题
Memcache的分布式介绍 memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能.服务器端仅包括内存存储功能,其实现非常简单.至于memcached的分布式,则是完全由客户 ...
- Kali-linux使用Easy-Creds工具攻击无线网络
Easy-Creds是一个菜单式的破解工具.该工具允许用户打开一个无线网卡,并能实现一个无线接入点攻击平台.Easy-Creds可以创建一个欺骗访问点,并作为一个中间人攻击类型运行,进而分析用户的数据 ...
- PAT——1057. 数零壹
给定一串长度不超过105的字符串,本题要求你将其中所有英文字母的序号(字母a-z对应序号1-26,不分大小写)相加,得到整数N,然后再分析一下N的二进制表示中有多少0.多少1.例如给定字符串“PAT ...
- Stacktrace: org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:5
jsp页面出现如下异常: Stacktrace: at org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServle ...
- CentOS7 安装 Docker 以及 Shipyard管理端
简介: Docker 是一个开源工具,它可以让创建和管理 Linux 容器变得简单.容器就像是轻量级的虚拟机,并且可以以毫秒级的速度来启动或停止. Docker 帮助系统管理员和程序员在容器中开发应用 ...
- 公司内网静态IP,外网无线动态IP 同时上网,不必再切换网卡啦 route 命令给你搞定。
一: 公司内网:192.168.55.101 255.255.255.0 192.168.55.1 网关 外网:192.168.20.101 255.255.255.0 192.16 ...