理解ConcurrentHashMap的实现原理

作者: 蓝灰_q | 来源:发表于2017-12-12 23:24 被阅读237次

    HashMap的并发问题

    Java7

    在Java7上,HashMap扩容Rehash的过程中,可能出现循环链表导致死循环的情况:
    Java7在Rehash时采用了倒序链表,因为Java7在Rehash中向链表插入Entry时,是向head前插入,所以形成了倒序。
    这在并发时可能导致链表死循环,比如:
    1.原始链表为3-->7-->null
    2.线程1rehash,拿到了3,然后被挂起
    3.线程2rehash,把链表重排,变成7-->3-->null
    4.线程1恢复执行,继续把3插到7前面,变成了7-->3-->7,循环链表,在查找时会变成死循环。

    Java8

    在Java8上,HashMap修改了Rehash的链表顺序,不再使用倒序,不会再出现死循环。
    但是,在并发读写的场景下,get和put的可能同时发生,不能保证数据正确,所以,Java8的HashMap仍然不是线程安全的。

    HashTable的并发问题

    HashTable和HashMap没有继承关系,HashTable继承了Dictionary抽象类和Map接口。
    HashTable在所有的方法上都加了方法锁,所以在单个方法的执行上是线程安全的。
    但是,HashTable的并发效率很差,同一时刻只能有一个线程操作。
    而且,HashTable是个遗留类,主要用来兼容遗留代码,如果要使用并发地HashMap,推荐使用的是ConcurrentHashMap,EventBus中就使用了ConcurrentHashMap来管理粘滞事件。

    ConcurrentHashMap

    Java7

    在Java7中,ConcurrentHashMap维持了16个segment(继承自ReentrantLock),每个segment存放一个HashMap,各segment之间可以并行。
    所以segment的hash散列会关系到并行效率,对key取hash后,利用hash的高位来计算属于哪个segment。
    获取size时,为了提高效率,会先不锁表,先对segment的值求和,连续三次,如果值不变就认为没有冲突,如果值变化了,再去锁表。

    Java8

    Java8的ConcurrentHashMap和Java8里的HashMap都使用红黑树(如果链表长度超过8,就使用红黑树管理)。
    在Java8中,ConcurrentHashMap做了进一步优化,不再划分segment,而是直接针对数组挂的每条链表都可以并行,同时,为了保持可见性,数组、Node的value,和Node的next都是volatile变量。
    在获取size时,不再做运算,而是在平时,put和remove时,维护map的size。

    参考

    Java8系列之重新认识HashMap
    Java进阶(六)从ConcurrentHashMap的演进看Java多线程核心技术

    相关文章

      网友评论

        本文标题:理解ConcurrentHashMap的实现原理

        本文链接:https://www.haomeiwen.com/subject/srebbxtx.html