Java多线程(四)之ConcurrentSkipListMap深刻分析

1、前言 

concurrentHashMap与ConcurrentSkipListMap性能测试

在4线程1.6万数据的条件下,ConcurrentHashMap 存取速度是ConcurrentSkipListMap 的4倍左右。 html

但ConcurrentSkipListMap有几个ConcurrentHashMap 不能比拟的优势 java

一、ConcurrentSkipListMap 的key是有序的。 node

二、ConcurrentSkipListMap 支持更高的并发。ConcurrentSkipListMap 的存取时间是log(N),和线程数几乎无关。也就是说在数据量必定的状况下,并发的线程越多,ConcurrentSkipListMap越能体现出他的优点。  算法


2、使用建议

在非多线程的状况下,应当尽可能使用TreeMap。此外对于并发性相对较低的并行程序可使用Collections.synchronizedSortedMap将TreeMap进行包装,也能够提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,可以提供更高的并发度。 安全


因此在多线程程序中,若是须要对Map的键值进行排序时,请尽可能使用ConcurrentSkipListMap,可能获得更好的并发度。
注意,调用ConcurrentSkipListMap的size时,因为多个线程能够同时对映射表进行操做,因此映射表须要遍历整个链表才能返回元素个数,这个操做是个O(log(n))的操做。
数据结构


2、什么是SkipList

Skip list(跳表)是一种能够代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,经过“空间来换取时间”的一个算法,在每一个节点中增长了向前的指针,在插入、删除、查找时能够忽略一些不可能涉及到的结点,从而提升了效率。 多线程


从几率上保持数据结构的平衡比显示的保持数据结构平衡要简单的多。对于大多数应用,用Skip list要比用树算法相对简单。因为Skip list比较简单,实现起来会比较容易,虽然和平衡树有着相同的时间复杂度(O(logn)),可是skip list的常数项会相对小不少。Skip list在空间上也比较节省。一个节点平均只须要1.333个指针(甚至更少)。
                
图1-1 Skip list结构图(以7,14,21,32,37,71,85序列为例)
并发


Skip list的性质

(1) 由不少层结构组成,level是经过必定的几率随机产生的。
(2) 每一层都是一个有序的链表,默认是升序,也能够根据建立映射时所提供的Comparator进行排序,具体取决于使用的构造方法。
(3) 最底层(Level 1)的链表包含全部元素。
(4) 若是一个元素出如今Level i 的链表中,则它在Level i 之下的链表也都会出现。
(5) 每一个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
app


3、什么是ConcurrentSkipListMap

ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上可以在O(log(n))时间内完成查找、插入、删除操做。
       注意,调用ConcurrentSkipListMap的size时,因为多个线程能够同时对映射表进行操做,因此映射表须要遍历整个链表才能返回元素个数,这个操做是个O(log(n))的操做。
框架


 ConcurrentSkipListMap存储结构



ConcurrentSkipListMap存储结构图

 

跳跃表(SkipList):(如上图所示)
1.多条链构成,是关键字升序排列的数据结构;
2.包含多个级别,一个head引用指向最高的级别,最低(底部)的级别,包含全部的key;
3.每个级别都是其更低级别的子集,而且是有序的;
4.若是关键字 key在 级别level=i中出现,则,level<=i的链表中都会包含该关键字key;


------------------------

ConcurrentSkipListMap主要用到了Node和Index两种节点的存储方式,经过volatile关键字实现了并发的操做

  

[java]  view plain copy
  1. static final class Node<K,V> {  
  2.         final K key;  
  3.         volatile Object value;//value值  
  4.         volatile Node<K,V> next;//next引用  
  5.         ……  
  6. }  
  7. static class Index<K,V> {  
  8.         final Node<K,V> node;  
  9.         final Index<K,V> down;//downy引用  
  10.        volatile Index<K,V> right;//右边引用  
  11.        ……  
  12. }  

------------------------

ConcurrentSkipListMap的查找


经过SkipList的方式进行查找操做:(下图以“查找91”进行说明:)

 


红色虚线,表示查找的路径,蓝色向右箭头表示right引用;黑色向下箭头表示down引用;

 

/get方法,经过doGet操做实现

 

[java]  view plain copy
  1. public V get(Object key) {  
  2.       return doGet(key);  
  3.  }  
  4.  //doGet的实现  
  5. private V doGet(Object okey) {  
  6.         Comparable<? super K> key = comparable(okey);  
  7.         Node<K,V> bound = null;  
  8.         Index<K,V> q = head;//把头结点做为当前节点的前驱节点  
  9.         Index<K,V> r = q.right;//前驱节点的右节点做为当前节点  
  10.         Node<K,V> n;  
  11.         K k;  
  12.         int c;  
  13.         for (;;) {//遍历  
  14.             Index<K,V> d;  
  15.             // 依次遍历right节点  
  16.             if (r != null && (n = r.node) != bound && (k = n.key) != null) {  
  17.                 if ((c = key.compareTo(k)) > 0) {//因为key都是升序排列的,全部当前关键字大于所要查找的key时继续向右遍历  
  18.                     q = r;  
  19.                     r = r.right;  
  20.                     continue;  
  21.                 } else if (c == 0) {  
  22.                     //若是找到了相等的key节点,则返回该Node的value若是value为空多是其余并发delete致使的,因而经过另外一种  
  23.                     //遍历findNode的方式再查找  
  24.                     Object v = n.value;  
  25.                     return (v != null)? (V)v : getUsingFindNode(key);  
  26.                 } else  
  27.                     bound = n;  
  28.             }  
  29.             //若是一个链表中right没能找到key对应的value,则调整到其down的引用处继续查找  
  30.             if ((d = q.down) != null) {  
  31.                 q = d;  
  32.                 r = d.right;  
  33.             } else  
  34.                 break;  
  35.         }  
  36.         // 若是经过上面的遍历方式,还没能找到key对应的value,再经过Node.next的方式进行查找  
  37.         for (n = q.node.next;  n != null; n = n.next) {  
  38.             if ((k = n.key) != null) {  
  39.                 if ((c = key.compareTo(k)) == 0) {  
  40.                     Object v = n.value;  
  41.                     return (v != null)? (V)v : getUsingFindNode(key);  
  42.                 } else if (c < 0)  
  43.                     break;  
  44.             }  
  45.         }  
  46.         return null;  
  47.     }  

------------------------------------------------

ConcurrentSkipListMap的删除


经过SkipList的方式进行删除操做:(下图以“删除23”进行说明:)

 


红色虚线,表示查找的路径,蓝色向右箭头表示right引用;黑色向下箭头表示down引用;

 

[java]  view plain copy
  1. //remove操做,经过doRemove实现,把全部level中出现关键字key的地方都delete掉  
  2. public V remove(Object key) {  
  3.         return doRemove(key, null);  
  4.  }  
  5.  final V doRemove(Object okey, Object value) {  
  6.         Comparable<? super K> key = comparable(okey);  
  7.         for (;;) {  
  8.             Node<K,V> b = findPredecessor(key);//获得key的前驱(就是比key小的最大节点)  
  9.             Node<K,V> n = b.next;//前驱节点的next引用  
  10.             for (;;) {//遍历  
  11.                 if (n == null)//若是next引用为空,直接返回  
  12.                     return null;  
  13.                 Node<K,V> f = n.next;  
  14.                 if (n != b.next)                    // 若是两次得到的b.next不是相同的Node,就跳转到第一层循环从新得到b和n  
  15.                     break;  
  16.                 Object v = n.value;  
  17.                 if (v == null) {                    // 当n被其余线程delete的时候,其value==null,此时作辅助处理,并从新获取b和n  
  18.                     n.helpDelete(b, f);  
  19.                     break;  
  20.                 }  
  21.                 if (v == n || b.value == null)      // 当其前驱被delet的时候直接跳出,从新获取b和n  
  22.                     break;  
  23.                 int c = key.compareTo(n.key);  
  24.                 if (c < 0)  
  25.                     return null;  
  26.                 if (c > 0) {//当key较大时就继续遍历  
  27.                     b = n;  
  28.                     n = f;  
  29.                     continue;  
  30.                 }  
  31.                 if (value != null && !value.equals(v))  
  32.                     return null;  
  33.                 if (!n.casValue(v, null))  
  34.                     break;  
  35.                 if (!n.appendMarker(f) || !b.casNext(n, f))//casNext方法就是经过比较和设置b(前驱)的next节点的方式来实现删除操做  
  36.                     findNode(key);                  // 经过尝试findNode的方式继续find  
  37.                 else {  
  38.                     findPredecessor(key);           // Clean index  
  39.                     if (head.right == null)   //若是head的right引用为空,则表示不存在该level  
  40.                         tryReduceLevel();  
  41.                 }  
  42.                 return (V)v;  
  43.             }  
  44.         }  
  45.     }  


-------------------------------------

ConcurrentSkipListMap的插入

 

经过SkipList的方式进行插入操做:(下图以“添加55”的两种状况,进行说明:)


在level=2(该level存在)的状况下添加55的图示:只需在level<=2的合适位置插入55便可

--------


在level=4(该level不存在,图示level4是新建的)的状况下添加55的状况:首先新建level4,而后在level<=4的合适位置插入55

-----------

[java]  view plain copy
  1. //put操做,经过doPut实现  
  2.  public V put(K key, V value) {  
  3.         if (value == null)  
  4.             throw new NullPointerException();  
  5.         return doPut(key, value, false);  
  6.  }  
  7. private V doPut(K kkey, V value, boolean onlyIfAbsent) {  
  8.         Comparable<? super K> key = comparable(kkey);  
  9.         for (;;) {  
  10.             Node<K,V> b = findPredecessor(key);//前驱  
  11.             Node<K,V> n = b.next;  
  12.            //定位的过程就是和get操做类似  
  13.             for (;;) {  
  14.                 if (n != null) {  
  15.                     Node<K,V> f = n.next;  
  16.                     if (n != b.next)               // 先后值不一致的状况下,跳转到第一层循环从新得到b和n  
  17.                         break;;  
  18.                     Object v = n.value;  
  19.                     if (v == null) {               // n被delete的状况下  
  20.                         n.helpDelete(b, f);  
  21.                         break;  
  22.                     }  
  23.                     if (v == n || b.value == null// b 被delete的状况,从新获取b和n  
  24.                         break;  
  25.                     int c = key.compareTo(n.key);  
  26.                     if (c > 0) {  
  27.                         b = n;  
  28.                         n = f;  
  29.                         continue;  
  30.                     }  
  31.                     if (c == 0) {  
  32.                         if (onlyIfAbsent || n.casValue(v, value))  
  33.                             return (V)v;  
  34.                         else  
  35.                             break// restart if lost race to replace value  
  36.                     }  
  37.                     // else c < 0; fall through  
  38.                 }  
  39.                 Node<K,V> z = new Node<K,V>(kkey, value, n);  
  40.                 if (!b.casNext(n, z))  
  41.                     break;         // restart if lost race to append to b  
  42.                 int level = randomLevel();//获得一个随机的level做为该key-value插入的最高level  
  43.                 if (level > 0)  
  44.                     insertIndex(z, level);//进行插入操做  
  45.                 return null;  
  46.             }  
  47.         }  
  48.     }  
  49.   
  50.  /** 
  51.      * 得到一个随机的level值 
  52.      */  
  53.     private int randomLevel() {  
  54.         int x = randomSeed;  
  55.         x ^= x << 13;  
  56.         x ^= x >>> 17;  
  57.         randomSeed = x ^= x << 5;  
  58.         if ((x & 0x8001) != 0// test highest and lowest bits  
  59.             return 0;  
  60.         int level = 1;  
  61.         while (((x >>>= 1) & 1) != 0) ++level;  
  62.         return level;  
  63.     }  
  64. //执行插入操做:如上图所示,有两种可能的状况:  
  65. //1.当level存在时,对level<=n都执行insert操做  
  66. //2.当level不存在(大于目前的最大level)时,首先添加新的level,而后在执行操做1   
  67. private void insertIndex(Node<K,V> z, int level) {  
  68.         HeadIndex<K,V> h = head;  
  69.         int max = h.level;  
  70.         if (level <= max) {//状况1  
  71.             Index<K,V> idx = null;  
  72.             for (int i = 1; i <= level; ++i)//首先获得一个包含1~level个级别的down关系的链表,最后的inx为最高level  
  73.                 idx = new Index<K,V>(z, idx, null);  
  74.             addIndex(idx, h, level);//把最高level的idx传给addIndex方法  
  75.         } else { // 状况2 增长一个新的级别  
  76.             level = max + 1;  
  77.             Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];  
  78.             Index<K,V> idx = null;  
  79.             for (int i = 1; i <= level; ++i)//该步骤和状况1相似  
  80.                 idxs[i] = idx = new Index<K,V>(z, idx, null);  
  81.             HeadIndex<K,V> oldh;  
  82.             int k;  
  83.             for (;;) {  
  84.                 oldh = head;  
  85.                 int oldLevel = oldh.level;  
  86.                 if (level <= oldLevel) { // lost race to add level  
  87.                     k = level;  
  88.                     break;  
  89.                 }  
  90.                 HeadIndex<K,V> newh = oldh;  
  91.                 Node<K,V> oldbase = oldh.node;  
  92.                 for (int j = oldLevel+1; j <= level; ++j)  
  93.                     newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);//建立新的  
  94.                 if (casHead(oldh, newh)) {  
  95.                     k = oldLevel;  
  96.                     break;  
  97.                 }  
  98.             }  
  99.             addIndex(idxs[k], oldh, k);  
  100.         }  
  101.     }  
  102. /** 
  103.      *在1~indexlevel层中插入数据  
  104.      */  
  105.     private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {  
  106.         //  insertionLevel 表明要插入的level,该值会在indexLevel~1间遍历一遍  
  107.         int insertionLevel = indexLevel;  
  108.         Comparable<? super K> key = comparable(idx.node.key);  
  109.         if (key == nullthrow new NullPointerException();  
  110.         // 和get操做相似,不一样的就是查找的同时在各个level上加入了对应的key  
  111.         for (;;) {  
  112.             int j = h.level;  
  113.             Index<K,V> q = h;  
  114.             Index<K,V> r = q.right;  
  115.             Index<K,V> t = idx;  
  116.             for (;;) {  
  117.                 if (r != null) {  
  118.                     Node<K,V> n = r.node;  
  119.                     // compare before deletion check avoids needing recheck  
  120.                     int c = key.compareTo(n.key);  
  121.                     if (n.value == null) {  
  122.                         if (!q.unlink(r))  
  123.                             break;  
  124.                         r = q.right;  
  125.                         continue;  
  126.                     }  
  127.                     if (c > 0) {  
  128.                         q = r;  
  129.                         r = r.right;  
  130.                         continue;  
  131.                     }  
  132.                 }  
  133.                 if (j == insertionLevel) {//在该层level中执行插入操做  
  134.                     // Don't insert index if node already deleted  
  135.                     if (t.indexesDeletedNode()) {  
  136.                         findNode(key); // cleans up  
  137.                         return;  
  138.                     }  
  139.                     if (!q.link(r, t))//执行link操做,其实就是inset的实现部分  
  140.                         break// restart  
  141.                     if (--insertionLevel == 0) {  
  142.                         // need final deletion check before return  
  143.                         if (t.indexesDeletedNode())  
  144.                             findNode(key);  
  145.                         return;  
  146.                     }  
  147.                 }  
  148.                 if (--j >= insertionLevel && j < indexLevel)//key移动到下一层level  
  149.                     t = t.down;  
  150.                 q = q.down;  
  151.                 r = q.right;  
  152.             }  
  153.         }  
  154.     }  

参考:

集合框架 Map篇(5)----ConcurrentSkipListMap http://hi.baidu.com/yao1111yao/item/0f3008163c4b82c938cb306d Java里多个Map的性能比较(TreeMap、HashMap、ConcurrentSkipListMap) http://blog.hongtium.com/java-map-skiplist/ 跳表SkipList的原理和实现 http://imtinx.iteye.com/blog/1291165 

相关文章
相关标签/搜索