范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

ConcurrentSkipListMap学习到跳表

  ConcurrentSkipListMap学习到跳表
  之前写过几篇关于多线程的学习笔记。但是写的可能比较乱,且不是很详细。看后面如果有时间会重新拿来搞一遍,也算是巩固自己的知识体系吧。这里其实主要是是要学习跳表是算法。 ConcurrentSkipListMap
  ConcurrentSkipListMap 多线程下安全的有序键值对。 ConcurrentSkipListMap是通过 跳表 来实现的。跳表是一个链表,但是通过使用"跳跃式"查找的方式使得插入、读取数据时复杂度变成了O(logn)。
  跳表(Skip List)是一种 "空间换时间" 的算法设计思想。我们来通过一个例子来看下Skip List 是如何实现查找的。
  首先我们按照重量大小排序的苹果100个(假设没有重量重复的苹果)。
  如果是普通的链表要找到重量为3Kg的苹果。如果不是跳表结构我们从第一个开始称重,然后第二个直到找到重量是3Kg的。
  如果是跳表则会有如下结构(暂时假设层级只有2)
  1kg
  2.95kg
  3.5kg
  6.7kg
  1kg
  1.1kg
  2.95kg
  ...
  3Kg
  3.5kg
  .....
  5kg
  ....
  6.7kg
  即可以理解为这100个苹果中有几个苹果是明确标签的标签上写着重量,但是我们要翻开标签才能看到。那么这个时候我们找3kg的苹果 的时候我们就可以这样做先翻开第一个标签发现是1kg,那么3kg肯定在后面。然后我们翻开第二个标签一看发现是2.95。3kg还在后面,我们继续翻标签找到第一个大于3的标签,那么3kg肯定在这个标签和上一个标签之间,我们只需要测量这中间的苹果即可找到3kg的苹果。这样查询效率就大大提高了。
  层数是根据一种随机算法得到的,为了不让层数过大,还会有一个最大层数 MAX_LEVEL 限制,随机算法生成的层数不得大于该值。
  在实际程序使用中标签肯定指会向一个下一层的一个苹果。且标签肯定是一个有序的,不然我们也无法快速定位了。 ConcurrentSkipListMap结构解析主要结构// 主要结构 private static final Object BASE_HEADER = new Object();  // 最底层链表的头指针BASE_HEADER private transient volatile HeadIndex head;         // 最上层链表的头指针head  // 普通结点 Node 定义 static final class Node {     final K key;     volatile Object value;     volatile Node next;      // ... }  // 头索引结点 HeadIndex static final class HeadIndex extends Index {     final int level;    // 层级     HeadIndex(Node node, Index down, Index right, int level) {         super(node, down, right);         this.level = level;     } }  // 索引结点 Index 定义  static class Index {         final Node node;      // node指向最底层链表的Node结点         final Index down;     // down指向下层Index结点         volatile Index right; // right指向右边的Index结点          // ... }Node:最底层链表中的结点,保存着实际的键值对,如果单独看底层链,其实就是一个按照Key有序排列的单链表。
  HeadIndex:结点是各层链表的头结点,它是Index类的子类,唯一的区别是增加了一个level字段,用于表示当前链表的级别,越往上层,level值越大。
  Index:结点是除底层链外,其余各层链表中的非头结点(见示意图中的蓝色结点)。每个Index结点包含3个指针:down、right、node。down和right指针分别指向下层结点和后继结点,node指针指向其最底部的node结点。、 构造方法// 构造方法 // 空Map. public ConcurrentSkipListMap() {     this.comparator = null;     initialize(); } // 指定比较器的构造器 public ConcurrentSkipListMap(Comparator<? super K> comparator) {     this.comparator = comparator;     initialize(); } // 从给定Map构建的构造器 public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {     this.comparator = null;     initialize();     putAll(m); } // 从给定SortedMap构建的构造器、并且Key的顺序与原来保持一致. public ConcurrentSkipListMap(SortedMap m) {     this.comparator = m.comparator();     initialize();     buildFromSorted(m); }  // 将一些字段置初始化null,然后将head指针指向新创建的HeadIndex结点。 private void initialize() {     keySet = null;     entrySet = null;     values = null;     descendingMap = null;     head = new HeadIndex(new Node(null, BASE_HEADER, null),null, null, 1); }主要方法put  // put public V put(K key, V value) {     if (value == null)          // ConcurrentSkipListMap的Value不能为null         throw new NullPointerException();     return doPut(key, value, false); }  private V doPut(K key, V value, boolean onlyIfAbsent) {     Node z;             // added node     if (key == null)		// key不能为空         throw new NullPointerException();     Comparator<? super K> cmp = comparator;          // 找到插入节点位置     outer: for (;;) {         for (Node b = findPredecessor(key, cmp), n = b.next;;) {             if (n != null) {                 Object v; int c;                 Node f = n.next;                 // 集中情况会导致本次插入失败,会重新进入outer循环                 // 1、 b.next被改动了 2、b.next被删掉了 3、b.next值变为空                 if (n != b.next)               // inconsistent read                     break;                 if ((v = n.value) == null) {   // n is deleted                     n.helpDelete(b, f);                     break;                 }                 if (b.value == null || v == n) // b is deleted                     break;                                  // 按照Key排序                 if ((c = cpr(cmp, key, n.key)) > 0) {                     b = n;                     n = f;                     continue;                 }                 // 当插入的key已经有值了就覆盖(CAS覆盖)                 if (c == 0) {                     if (onlyIfAbsent || n.casValue(v, value)) {                         @SuppressWarnings("unchecked") V vv = (V)v;                         return vv;                     }                     break; // restart if lost race to replace value                 }                 // else c < 0; fall through             }              z = new Node(key, value, n);             if (!b.casNext(n, z))                 break;         // restart if lost race to append to b             break outer;         }     }      // 判断是否需要扩层     // 0x80000001展开为二进制为10000000000000000000000000000001     // 这里(rnd & 0x80000001) == 0     // 相当于排除了负数(负数最高位是1),排除了奇数(奇数最低位是1)     // 只有最高位最低位都不为1的数跟0x80000001做&操作才会为0,也就是正偶数         int rnd = ThreadLocalRandom.nextSecondarySeed();     if ((rnd & 0x80000001) == 0) { // test highest and lowest bits                  // 默认level为1,也就是只要到这里了就会至少建立一层索引         int level = 1, max;         // 随机数从最低位的第二位开始,有几个连续的1则level就加几         while (((rnd >>>= 1) & 1) != 0)             ++level;         Index idx = null;         HeadIndex h = head;         // 新建一个目标节点的索引节点(Index)         if (level <= (max = h.level)) {             for (int i = 1; i <= level; ++i)                 idx = new Index(z, idx, null);         }         else { // try to grow by one level             // 如果最高层级大于原来的最高层级,则加一层索引             level = max + 1; // hold in array and later pick the one to use             @SuppressWarnings("unchecked")Index[] idxs =                 (Index[])new Index<?,?>[level+1];             for (int i = 1; i <= level; ++i)                 idxs[i] = idx = new Index(z, idx, null);             for (;;) {                 // 原头节点                 h = head;                 // 原头节点层级                 int oldLevel = h.level;                 // 如果重新规划的层级小于或等于取出来的层级,说明头部节点被其他线程动过                 // 因为进到这个esle就是要增加层级的,这里就不做后面的处理                 if (level <= oldLevel) // lost race to add level                     break;                 HeadIndex newh = h;                 Node oldbase = h.node;                 for (int j = oldLevel+1; j <= level; ++j)                     newh = new HeadIndex(oldbase, newh, idxs[j], j);                 if (casHead(h, newh)) {                     h = newh;                     idx = idxs[level = oldLevel];                     break;                 }             }         }                  // 经过上面的步骤,有两种情况         // 一是没有超出高度,新建一条目标节点的索引节点链         // 二是超出了高度,新建一条目标节点的索引节点链,同时最高层头索引节点同样往上长         // find insertion points and splice in         splice: for (int insertionLevel = level;;) {             int j = h.level;             for (Index q = h, r = q.right, t = idx;;) {                 if (q == null || t == null)                     break splice;                 if (r != null) {                     Node n = r.node;                     // compare before deletion check avoids needing recheck                     int c = cpr(cmp, key, n.key);                     if (n.value == null) {                         if (!q.unlink(r))                             break;                         r = q.right;                         continue;                     }                     if (c > 0) {                         q = r;                         r = r.right;                         continue;                     }                 }                  if (j == insertionLevel) {                     if (!q.link(r, t))                         break; // restart                     if (t.node.value == null) {                         findNode(key);                         break splice;                     }                     if (--insertionLevel == 0)                         break splice;                 }                  if (--j >= insertionLevel && j < level)                     t = t.down;                 q = q.down;                 r = q.right;             }         }     }     return null; }	  // 查找小于且最接近给定key的Node结点 private Node findPredecessor(Object key, Comparator<? super K> cmp) {     if (key == null)         throw new NullPointerException(); // don"t postpone errors     for (;;) {         for (Index q = head, r = q.right, d;;) {             if (r != null) {                 Node n = r.node;                 K k = n.key;                 if (n.value == null) {                     if (!q.unlink(r))                         break;           // restart                     r = q.right;         // reread r                     continue;                 }                 // key大于k,继续向右查找                 if (cpr(cmp, key, k) > 0) {                      q = r;                     r = r.right;                     continue;                 }             }             // 已经到了level1的层、直接返回对应的Node结点             if ((d = q.down) == null)                 return q.node;                          //  转到下一层,继续查找(level-1层)             q = d;             r = d.right;         }     } }整个插入过程分成三个部分:
  1、找到目标节点的位置并插入
  找到离目标节点最近的索引节点对应的数据节点;
  从这个数据节点开始往后遍历,直到找到目标节点应该插入的位置;(当插入的key已经有值了就覆盖)
  2、随机决定是否需要建立索引及其层次,如果需要则建立自上而下的索引
  看运气决定是否创建索引(rnd & 0x80000001 正偶数变化)
  算出来的层级不高于现有最高层级,则直接建立一条竖直的索引链表(right没值),并结束此部分
  算出来的层级高于现有最高层级,则新的层级为旧层级加1,同样建立一条竖直的索引链表(right没值),将头索引也向上增加到相应的高度,并结束此部分
  3、将新建的索引节点(包含头索引节点)与其它索引节点通过右指针连接在一起(补上right指针)
  从最高层级的头索引节点开始,向右遍历,找到目标索引节点的位置;
  如果当前层有目标索引,则把目标索引插入到这个位置,并把目标索引前一个索引向下移一个层级;
  如果当前层没有目标索引,则把目标索引位置前一个索引向下移一个层级;同样地,再向右遍历,寻找新的层级中目标索引的位置重复上面的步骤。
  依次循环找到所有层级目标索引的位置并把它们插入到横向的索引链表中
  remove & doRemove在删除键值对时,不会立即执行删除,而是通过引入 "标记结点" ,以 "懒删除" 的方式进行,以提高并发效率。  public V remove(Object key) {     return doRemove(key, null); }  final V doRemove(Object key, Object value) {     if (key == null)         throw new NullPointerException();     Comparator<? super K> cmp = comparator;     outer: for (;;) {         // 寻找目标节点之前的最近的索引节点对应的数据节点         // 为了方便,这里叫b为当前节点,n为下一个节点,f为下下个节点         for (Node b = findPredecessor(key, cmp), n = b.next;;) {             Object v; int c;             // 和新增类似做几个检查             if (n == null)                 break outer;             Node f = n.next;             if (n != b.next)                    // inconsistent read                 break;             // 下个节点的值也为null了,说明有其它线程标记该元素为删除状态了             if ((v = n.value) == null) {        // n is deleted                 n.helpDelete(b, f);                 break;             }             if (b.value == null || v == n)      // b is deleted                 break;             // 如果c<0,说明没找到元素,退出外层循环             if ((c = cpr(cmp, key, n.key)) < 0)                 break outer;             // 如果c>0,说明还没找到,继续向右找             if (c > 0) {                 b = n;                 n = f;                 continue;             }             // c=0,说明n就是要找的元素             // 如果value不为空且不等于找到元素的value,不需要删除,退出外层循环             if (value != null && !value.equals(v))                 break outer;             if (!n.casValue(v, null))                 // 如果删除失败,说明其它线程先一步修改了                 break;                          // 到了这里n的值肯定是设置成null了             // 使用cas将n的下一个节点指向自己并且b的next为n的next(这里虽然用的|| 但要注意!)             // 意思就是这里要保证要删除的节点的next指针指向自己,并且自己的上一个节点的指针要指向自己的next节点             if (!n.appendMarker(f) || !b.casNext(n, f))                 // 使用这个方法尝试删除,其实就是最开始的校验最后调用的还是helpDelete()                 findNode(key);                  // retry via findNode             else {                 // 处理索引 里面有一个unlink                 findPredecessor(key, cmp);      // clean index                 // 如果最高层头索引节点没有右节点,则跳表的高度降级                 if (head.right == null)                     tryReduceLevel();             }             @SuppressWarnings("unchecked") V vv = (V)v;             return vv;         }     }     return null; }  // Node.class中的方法,协助删除元素 void helpDelete(Node b, Node f) {     /*      * Rechecking links and then doing only one of the      * help-out stages per call tends to minimize CAS      * interference among helping threads.      */     // 这里的调用者this==n,三者关系是b->n->f     if (f == next && this == b.next) {         // 将n的值设置为null后,会先把n的下个节点设置为marker节点         // 这个marker节点的值是它自己         // 这里如果不是它自己说明marker失败了,重新marker         if (f == null || f.value != f) // not already marked             casNext(f, new Node(f));         else             // marker过了,就把b的下个节点指向marker的下个节点             b.casNext(this, f.next);     } }1、寻找目标节点之前最近的一个索引对应的数据节点(数据节点都是在最底层的链表上);2、从这个数据节点开始往后遍历,直到找到目标节点的位置;
  3、如果这个位置没有元素,直接返回null,表示没有要删除的元素
  4、如果这个位置有元素,先通过n.casValue(v, null)原子更新把其value设置为null;
  5、通过n.appendMarker(f)和b.casNext(n, f)修改数据节点的指针
  6、通过findPredecessor(key, cmp)中的q.unlink(r)删除索引节点;
  7、head的right指针指向了null,则跳表高度降级;get public V get(Object key) {     return doGet(key); }   private V doGet(Object key) {     // key不为空     if (key == null)         throw new NullPointerException();     Comparator<? super K> cmp = comparator;          outer: for (;;) {         // 寻找目标节点之前最近的索引对应的数据节点         // 为了方便,这里叫b为当前节点,n为下个节点,f为下下个节点         for (Node b = findPredecessor(key, cmp), n = b.next;;) {             Object v; int c;             // 常规检查             if (n == null)                 break outer;             Node f = n.next;             if (n != b.next)                // inconsistent read                 break;             // 如果n的值为空,说明节点已被其它线程标记为删除             if ((v = n.value) == null) {    // n is deleted                 // 协助删除,再重试                 n.helpDelete(b, f);                 break;             }             // 如果b的值为空或者v等于n,说明b已被删除             // 这时候n就是marker节点,那b就是被删除的那个             if (b.value == null || v == n)  // b is deleted                 break;             // 如果c==0,说明找到了元素,就返回元素值             if ((c = cpr(cmp, key, n.key)) == 0) {                 @SuppressWarnings("unchecked") V vv = (V)v;                 return vv;             }             // 如果c<0,说明没找到元素             if (c < 0)                 break outer;             // 如果c>0,说明还没找到,继续寻找             // 当前节点往后移             b = n;             // 下一个节点往后移             n = f;         }     }     return null; }
  源码部分参考文章:
  https://blog.csdn.net/weixin_30569033/article/details/96893761
  https://mp.weixin.qq.com/s?__biz=MzkxNDEyOTI0OQ==&mid=2247484459&idx=1&sn=b4e7db9fdf256dfb312a2a90fcde79b3&source=41#wechat_redirect Redis里面的跳表
  参考文档:
  https://www.cnblogs.com/mxxct/p/13857494.html
  https://www.cnblogs.com/lfri/p/9991925.html
  https://www.cnblogs.com/hunternet/p/11248192.html
  redis 数据类型 zset 实现有序集合,底层使用的数据结构是跳表。因为redis是用C写的。我基本已经把上学时候学的C都还给老师了,这里就不看源码了。看上面的几篇博客也就理解了。
  这里搬运下上面博客中的几句话:
  为什么使用跳跃表,而不是平衡树等用来做有序元素的查找 跳跃表的时间复杂度和红黑树是一样的,而且实现简单 在并发的情况下,红黑树在插入删除的时候可能需要做rebalance的操作,这样的操作可能会涉及到整个树的其他部分;而链表的操作就会相对局部,只需要关注插入删除的位置即可,只要多个线程操作的地方不一样,就不会产生冲突

CrucialP5500GBSSD评测打开数据通路,驾驭高负荷应用随着数字化的不断深入,数据的重要性与日俱增,已然成为企业和个人的核心资产。同时,PC游戏影像编辑三维动画等高阶应用也对数据的存储和传输性能提出了越来越高的要求。在这样的市场形势下,西部数据全新NVMeSSD系列突破性能局限,加速数据中心转型升级数据中心的格局在产生实质性的变化,NVMe全闪存架构风起云涌。即便它只是站在起跑线,但趋势已然形成。在这个迎风起飞的行业风口,西部数据前瞻性地推出了全新的双端口高性能Ultrast喷墨新势力京瓷新品发布会亮相2020全印展2020年10月12日,第八届中国国际全印展正式在上海新国际博览中心开幕。全球领先的打印设备品牌京瓷在展会中带来了一场主题为喷墨新势力的喷墨新品发布会,新晋推出的TASKalfaP净享好水,德国卡赫创新净水系列亮相进博会11月5日,第三届中国国际进口博览会开幕,卡赫净水产品作为德国卡赫的热点产品之一更是备受瞩目。台上净水器WPD60P厨下净水器600G800G1000G在卡赫消费品展区重磅亮相。卡华硕灵耀X新品上市首款英特尔EVO笔记本9月30日,华硕正式发布了灵耀X系列两款重磅新品灵耀X逍遥灵耀X纵横。作为华硕笔记本旗下的高端商务系列,灵耀X凝聚了华硕最尖端的设计工艺以及最前沿的产品技术。灵耀X纵横及灵耀X逍遥持续深入头发科学,戴森Corrale美发直发器让头发尽在掌控头发毛躁易断,头发造型总不如理想状态,头发的失控在生活中处处可觅。为此,戴森投入了近十年的时间,投资了超过1亿英镑在全球成立多个头发科学实验室,与数百名头发领域的科学家工程师和专业大快人心!苹果13发布在即,中国巨头随之宣布了智能眼镜的到来随着苹果13的发布的到来,越来越多的厂商也急忙发布了新一轮的策略。但让笔者没想到的是,就在苹果13发布会的前夕,一家中国科技巨头正式宣布了智能眼镜的到来,作为未来最具有代表的眼镜,石头再次引领全球,自清洁扫拖体验全面升级如今的时代已经今非昔比了。5G的发布了也加快了科技的步伐。智能家居的诞生,在到扫地机器人的到来。这无一不是让我们对生活更加的方便和造福。当我们每天回到家就能够看到家里干干净净的地板中国巨头再次传来轰炸性消息,引起了网上一片沸腾!库克哭晕近些天来,又是iphone13的发售又是中国厂商的新品发布!这在过去是所没有的,而如今能够在iphone13发布后全面狙击也是再一次验证了中国科技的实力!西方国家一直忌惮于中国科技品位与格调,Klipschx迈凯伦合作款真无线耳机登场日前,以科技为先的国际顶级音响品牌Klipsch杰士,在中国市场推出了万众期待的T5II迈凯伦(McLaren)合作款真无线耳机系列。Klipschx迈凯伦Klipsch杰士与Mc京东方亮相CMEF医疗展创新模式塑造全生命周期智慧健康服务在2021中国国际医疗器械博览会(CMEF)上,作为全球物联网创新企业,BOE(京东方)携多款行业领先的智慧健康医疗领域的产品和解决方案强势亮相,全面展示其智慧医工事业的强大实力和
190元英特尔工作站X系列酷睿I7处理器沦为3060显卡破解挖矿亮机U之前为了评测X系列的处理器,魔改君从某猫购买过一张全新的华擎X299KILLER主板,不过测试结果大家也知道了,确实不尽人意。留着无用,自然就7天无理由退货了。如果没记错,当时的价100元微星B450主板,CPU核显跑分6万大战绝地求生竟能流畅吃鸡之前给AMD的锐龙4代APU处理器4350G挖了坑,没等闻到香气,就瞬间售罄。然后,悲催的价格从1030元涨到了1259元。映泰方面虽然送来了499元的B550MSLIVER主板,300元华强北国产魔改I7处理器又出正式版?有望成为下一个黑科技300多元的魔改I7处理器是什么玩意?熟悉的小伙伴们自然已经猜到是QNCT这颗ES版的笔记本酷睿I78850H了。这货虽然刚开始的时候是300多元,但现在已经450了。基于魔改君身不是2499元!最低5599元!新版3060显卡预售价格出现,何时矿难?亲爱的小伙伴们,你们所熟悉的那个,对硬件新闻完全不感冒,不相信的魔改君这次难得地发表了一篇新闻类的文章!某星的某东店铺,前几天进行了一场新版本的RTX306012G显卡的预售活动,速度高达100G每秒!1000元3600频率16G内存4条64G英特尔视频平台拖着疲惫的身躯,给粉丝留下一片作业文章!平台如下英特尔X系列10980XE处理器(不锁内存不锁CPU频率)主板,微星X299RAIDER内存,16GDJR36004一般选择英特尔工60元3热管铜底散热器力压英特尔11代I9处理器,电脑小机箱福音从沈阳出发跑来跑去大概半个月了,终于找到了落脚的地方。亲爱的粉丝们,魔改君终于暂时落脚广东,开始为你们寻找新的产品。最近的散热器市场,雅浚B3一直独领风骚,据说跟利民也有点关系,不198元8GD4内存条额外多了双通道?900元10代I5处理器组电脑新思路有毒的魔改君,成功地发布了一篇国产长鑫颗粒内存的文章。价格199元,频率3000,时序优秀。然后成功地让它瞬间售罄,再无性价比可言!于是有粉丝怒了!一方面,文章成了空谈!另外一方面中报光大信用卡2021年中期业绩,新增发卡350万张光大信用卡2021年上半年业绩发布,累计发卡量8390。25万张,新增发卡349。98万张,较上年末增长4。35光大信用卡透支余额4413。33亿元(不含在途挂账调整),比上年末下中报华夏信用卡2021年中期业绩,实现业务收入75亿元华夏信用卡2021年上半年业绩发布,累计发卡2894。38万张,较上年末增长7。14有效卡量1943。01万张,较上年末增长4。12有效户数1634。43万户,较上年末增长2。47中报交银信用卡2021年中期业绩,整体处于调整状态交银信用卡2021年上半年业绩发布,在册卡量7377万张,较上年末增长1。52交银信用卡透支余额4632。42亿元,较上年末下降0。16交银信用卡交易金额14515。56亿元,同比中报郑州银行信用卡2021年中期业绩,累计发卡量超过50万张郑州银行信用卡2021年上半年业绩发布,累计发卡54。32万张,较上年末增长10。22郑银信用卡贷款余额30。31亿元,较上年末增长2。6郑银信用卡消费金额113。91亿元,实现2