zoukankan      html  css  js  c++  java
  • ConcurrentHashMap之实现细节

    ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。在这之前我对ConcurrentHashMap只有一些肤浅的理解,仅知道它采用了多个锁,大 概也足够了。但是在经过一次惨痛的面试经历之后,我觉得必须深入研究它的实现。面试中被问到读是否要加锁,因为读写会发生冲突,我说必须要加锁,我和面试 官也因此发生了冲突,结果可想而知。还是闲话少说,通过仔细阅读源代码,现在总算理解ConcurrentHashMap实现机制了,其实现之精巧,令人 叹服,与大家共享之。

    实现原理

    ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修 改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

    有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作 完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的, 并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出 现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。

    Java代码
    1. /**
    2. * The segments, each of which is a specialized hash table
    3. */  
    4. final Segment<K,V>[] segments;  
    /** * The segments, each of which is a specialized hash table */ final Segment<K,V>[] segments;

    不变(Immutable)和易变(Volatile)

    ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可 以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可 变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:

    Java代码
    1. static final class HashEntry<K,V> {  
    2.     final K key;  
    3.     final int hash;  
    4.     volatile V value;  
    5.     final HashEntry<K,V> next;  
    6. }  
    static final class HashEntry<K,V> { final K key; final int hash; volatile V value; final HashEntry<K,V> next; }

    可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这 就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的 值,将value设置成volatile,这避免了加锁。


    其它

    为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。当并发级别 为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。但是我们也不要忘记《算法导论》给我们的教训:hash槽的的个数不应该是 2^n,这可能导致hash槽分配不均,这需要对hash值重新再hash一次。(这段似乎有点多余了

    这是重新hash的算法,还比较复杂,我也懒得去理解了。

    Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20private%20static%20int%20hash(int%20h)%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20Spread%20bits%20to%20regularize%20both%20segment%20and%20index%20locations%2C%0A%20%20%20%20%20%20%20%20%2F%2F%20using%20variant%20of%20single-word%20Wang%2FJenkins%20hash.%0A%20%20%20%20%20%20%20%20h%20%2B%3D%20(h%20%3C%3C%20%2015)%20%5E%200xffffcd7d%3B%0A%20%20%20%20%20%20%20%20h%20%5E%3D%20(h%20%3E%3E%3E%2010)%3B%0A%20%20%20%20%20%20%20%20h%20%2B%3D%20(h%20%3C%3C%20%20%203)%3B%0A%20%20%20%20%20%20%20%20h%20%5E%3D%20(h%20%3E%3E%3E%20%206)%3B%0A%20%20%20%20%20%20%20%20h%20%2B%3D%20(h%20%3C%3C%20%20%202)%20%2B%20(h%20%3C%3C%2014)%3B%0A%20%20%20%20%20%20%20%20return%20h%20%5E%20(h%20%3E%3E%3E%2016)%3B%0A%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704267" lk_media="yes">
    1. private static int hash(int h) {  
    2.     // Spread bits to regularize both segment and index locations,  
    3.     // using variant of single-word Wang/Jenkins hash.  
    4.      h += (h <<  15) ^ 0xffffcd7d;  
    5.      h ^= (h >>> 10);  
    6.      h += (h <<   3);  
    7.      h ^= (h >>>  6);  
    8.      h += (h <<   2) + (h << 14);  
    9.     return h ^ (h >>> 16);  
    10. }  
    private static int hash(int h) { // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }

    这是定位段的方法:

    Java代码
    1. final Segment<K,V> segmentFor(int hash) {  
    2.     return segments[(hash >>> segmentShift) & segmentMask];  
    3. }  
    final Segment<K,V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; }

  • 相关阅读:
    Web SSH 客户端Ajaxterm安装
    Ubuntu Manpage: ajaxterm
    Web工程师的工具箱 | 酷壳
    EF架构~二级域名中共享Session
    VS~通过IIS网站启用"域名"调试
    EF架构~豁出去了,为了IOC,为了扩展,改变以前的IRepository接口
    MVVM架构~knockoutjs系列之文本框数符长度动态统计功能
    JS~jwPlayer为js预留的回调方法大总结
    晒网站:应用诺贝尔奖得主罗斯匹配算法的交友网站,具有更符合用户大网撒鱼心理的新颖用户使用模式
    ZOJ 2334(Monkey King-左偏树第一题)
  • 原文地址:https://www.cnblogs.com/danghuijian/p/4400695.html
Copyright © 2011-2022 走看看