如何设计线程安全的 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 的方式获取头指定槽的头节点,避免其他线程同时对头节点做出修改。
相关推荐
lemon_sjdk4 分钟前
软件开发模式架构选择
java·架构·软件开发·前后端分离
李高钢13 分钟前
c#获取当前程序所在目录避坑
开发语言·数据库·c#
IT北辰17 分钟前
用 Python 实现连续数据分组求和并回写
开发语言·python
董广明17 分钟前
单元测试(JUnit、Mockito、PowerMock )
java·经验分享·junit·单元测试
Lisonseekpan17 分钟前
Java Stream 流式编程
java·后端
沐浴露z34 分钟前
Kafka Consumer 消费流程详解
java·分布式·kafka
程序员小假37 分钟前
运行时有出现过什么异常?
java·后端
凤山老林41 分钟前
还在用JDK8?JDK8升级JDK11:一次价值千万的升级指南
java·开发语言·jvm·spring boot·后端·jdk
lang201509281 小时前
Apache Maven 项目的开发指南
java·maven·apache
沐知全栈开发1 小时前
Servlet 数据库访问
开发语言