• HashMap为什么线程不安全?


    JDK1.7 及之前版本,在多线程环境下,HashMap 扩容时会造成死循环数据丢失的问题。

    死循环问题:

    这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,使得查询元素的操作陷入死循环而无法结束

    为解决这个问题,JDK1.8版本的HashMap 采用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。

    但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在数据覆盖的问题。并发环境下,推荐使用 ConcurrentHashMap 。

     数据丢失问题在 JDK1.7 和 JDK 1.8 中都存在,这里以 JDK1.8 为例。

    JDK1.8 后,在 HashMap 中,多个键值对可能会被分配到同一个桶,并以链表或红黑树的形式存储。多个线程对 HashMap put 操作会导致线程不安全,具体来说会有数据覆盖的风险。

    举个例子:

    • 两个线程 1,2 同时进行 put 操作,并且发生了哈希冲突(hash 函数计算出的插入下标是相同的)
    • 不同的线程可能在不同的时间片获得 CPU执行的机会,当前现场 1 执行完哈希冲突判断后,由于时间片耗尽挂起。线程 2 先完成了插入操作。
    • 随后,线程 1 获得时间片,由于之前已经进行过 hash 碰撞的判断,所以此时会直接进行插入,这就导致线程2插入的数据被线程 1 覆盖了。
    1. public V put(K key, V value) {
    2.     return putVal(hash(key), key, value, false, true);
    3. }
    4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    5.                    boolean evict) {
    6.     // ...
    7.     // 判断是否出现 hash 碰撞
    8.     // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    9.     if ((p = tab[i = (n - 1) & hash]) == null)
    10.         tab[i] = newNode(hash, key, value, null);
    11.     // 桶中已经存在元素(处理hash冲突)
    12.     else {
    13.     // ...
    14. }

    还有一种情况是这两个线程同时 put 操作导致 size 的值不正确,进而导致数据覆盖的问题:

    1. 线程 1 执行 if (++size > threshold) 判断时,假设获得 size 的值为 10,由于时间片耗尽挂起
    2. 线程 2 也执行 if (++size > threshold) 判断,获得 size 的值也为 10,并将元素插入到该桶中,并将 size 的值更新为 11.
    3. 随后,线程 1 获得时间片,它也将元素放入桶中,并将 size 的值更新为 11.
    4. 线程 1 , 2 都执行了一次 put 操作,但是 size 的值只增加了 1 ,也就导致实际上只有一个元素被添加到了 HashMap 中。
       
    1. public V put(K key, V value) {
    2. return putVal(hash(key), key, value, false, true);
    3. }
    4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    5. boolean evict) {
    6. // ...
    7. // 实际大小大于阈值则扩容
    8. if (++size > threshold)
    9. resize();
    10. // 插入后回调
    11. afterNodeInsertion(evict);
    12. return null;
    13. }

    所以在并发操作中一般不使用 HashMap ,如果需要使用 Map 的话 可以使用 CurrentHashMap。

  • 相关阅读:
    数据库管理-第117期 拿下19c OCM(202301121)
    SQL sever2008数据库备份、还原以及库检查
    Vsan数据恢复—Vsan存储断电导致虚拟机无法启动的数据恢复案例
    使用linux命令定时备份还原数据库
    连接池快速入门
    mybatis-plus的分页变化
    RabbitMQ 死信队列
    艾美捷ProSci丨ProSci HIV-1 p24 抗体解决方案
    【全志T113-S3_100ask】13-1 Linux c语言ioctl驱动oled(iic、ssd1306)屏幕
    [附源码]计算机毕业设计基于springboot学习互助辅助系统
  • 原文地址:https://blog.csdn.net/qq_48626761/article/details/133786129