如何设计线程安全的 HashMap?

如何设计线程安全的 HashMap?

HashMap 线程不安全的体现:

  1. 多线程下扩容死循环:JDK1.7中的 HashMap 使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
  2. 多线程的put可能导致元素的丢失:多线程同时执行 put 操作,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。此问题在JDK 1.7和 JDK 1.8 中都存在。
  3. put和get并发时,可能导致get为null:线程1执行 put 时,因为元素个数超出 threshold 而导致 rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK 1.7和 JDK 1.8 中都存在。
    针对问题1,JDK1.8 使用尾插法已经解决了,因此我们需要重点解决问题2和问题3。
    思路一:使用 Synchronized 来实现线程安全的,给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞等待需要的锁被释放。
    优点:实现简单。
    缺点:竞争激烈的多线程场景中性能会变的很差。
    思路二:使用 ConcurrentHashMap JDK1.7 的实现思路, 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
    优点:并发访问率比Synchronized更高,效率也更高。
    思路三:使用 ConcurrentHashMap JDK1.8 的实现思路,采用CAS + synchronized实现更加低粒度的锁。
    优点:将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点(红黑树的根节点),就不会影响其他的哈希桶元素的读写,大大提高了并发度。
    CAS 机制在 ConcurrentHashMap 的具体体现:
  4. 在初始化数组时,它会以 CAS 的方式修改初始化状态,避免多个线程同时进行初始化;
  5. 在执行 put 方法初始化头节点时,它会以 CAS 的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点;
  6. 在数组扩容时,每个线程会以 CAS 方式修改任务序列号来争抢扩容任务,避免和其他线程产生冲突;
  7. 在执行 get 方法时,它会以 CAS 的方式获取头指定槽的头节点,避免其他线程同时对头节点做出修改。
相关推荐
皮皮林5511 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河2 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
桦说编程5 小时前
从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想
java·后端·源码阅读
躺平大鹅6 小时前
Java面向对象入门(类与对象,新手秒懂)
java
初次攀爬者7 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺7 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
Derek_Smart9 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP10 小时前
MyBatis-mybatis入门与增删改查
java
孟陬13 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端