• HashMap与ConcurrentMap


    HashMap是线程不安全的,在多线程环境下对某个对象中HashMap类型的实例变量进行操作时,可能会产生各种不符合预期的问题。

    HashMap到底会出现什么问题

    当在多线程并发向HashMap中插入(put)数据时候,可能会出现数据丢失。

    我们知道HashMap是数组+链表在极端情况下会出现树的结构。

    我们假设当前HashMap中table的状态如下:

    此时t1和t2同时执行put,假设t1执行put(“key2”, “value2”),t2执行put(“key3”, “value3”),并且key2和key3的hash值与图中的key1相同。

    那么正常情况下,put完成后,table的状态应该是下图二者其一

     或者

     

    此时我们来看看异常情况,部分源码如下

    1. if ((e = p.next) == null) { // #1
    2. p.next = newNode(hash, key, value, null); // #2
    3. }

    我们假设,有这两个线程都执行到了#1代码位置,然后都判断当前key1处没有数据,需要新插入一个节点到key1中,此时会发生数据覆盖现象

     我们假设第一个线程插入成功

     另一个线程在执行代码时候就会将上一个线程插入的数据覆盖

     此时就发生了线程安全问题

    ConcurrentHashMap如何解决线程安全问题?

    Jdk7中ConcurrentHashMap的实现原理基于分段锁机制。

    如何实现呢?这就用到了ConcurrentHashMap中最关键的Segment。

    ConcurrentHashMap中维护着一个Segment数组,每个Segment可以看做是一个HashMap

    而Segment本身继承了ReentrantLock,它本身就是一个锁。

    在Segment中通过HashEntry数组来维护其内部的hash表。

    思路如下

    如果你是多线程来操作ConcurrentHashMap,如果你们操作的Segment不一样,就可以同时进行,如果操作的是同一个Segment就需要串行处理,只能等待锁释放。

    所以,JDK7中,ConcurrentHashMap的整体结构可以描述为下图这样子。

    这也就是前文所说的每个Segment可以看做是一个HashMap。

    JDK8与JDK7的实现由较大的不同,JDK8中不在使用Segment的概念,他更像HashMap的实现方式。

    关于HashMap的原理,参考另一篇文章 HashMap原理及内部存储结构

    这个结构基本和HashMap类型,主要是通过volatile和CAS等操作保证线程安全。

     

    其中put操作的共享变量的存储和读取全部通过volatile或CAS的方式,保证了线程安全。

    对应源码如下

    1. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    2. if (casTabAt(tab, i, null,
    3. new Node(hash, key, value, null)))
    4. break; // no lock when adding to empty bin
    5. }
    6. static final Node tabAt(Node[] tab, int i) {
    7. return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    8. }
    9. static final boolean casTabAt(Node[] tab, int i,
    10. Node c, Node v) {
    11. return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    12. }

    参考

    参考1ConcurrentHashMap怎么解决多线程问题

    参考2HashMap有何问题

    总结如下:

    • HashMap并发异常的例子,当多线程操作HashMap的put操作时,如果两个线程操作的元素的Hash值相同,在插入到HashMap中会出现并发异常问题,可能会造成数据覆盖。
    • concurrentHashMap的解决思路,在JDK7中通过引入分段锁机制,引入Segment,Segment继承了ReentrantLock。它本身就是一个锁。在Segment中通过HashEntry数组来维护其内部的hash表。如果你是多线程来操作ConcurrentHashMap,如果你们操作的Segment不一样,就可以同时进行,如果操作的是同一个Segment就需要串行处理,只能等待锁释放。
    • 在JDK8中ConcurrentHashMap,在多线程操作ConcurrentHashMap整个过程中,共享变量的存储和读取全部通过volatile或CAS的方式,保证了线程安全。

     

  • 相关阅读:
    长假回归,回顾一下所有的电商API接口
    k8s--基础--22.11--storageclass--类型--Azure 文件
    算法思想之回溯法
    软考高级之132个工具和技术
    Idea 设置 tab 设置为 4个空格
    Flink学习8:数据的一致性
    Linux常用命令
    高等数学(第七版)同济大学 习题4-2(前半部分) 个人解答
    从源码级深入剖析Tomcat类加载原理
    学习 xss+csrf 组合拳
  • 原文地址:https://blog.csdn.net/abc123mma/article/details/127552137