八股训练营第 29 天 | HashMap 为什么是线程不安全的? 如何实现线程安全?concurrentHashMap 如何保证线程安全?HashMap和ConcurrentHashMap的区别?

HashMap 为什么是线程不安全的? 如何实现线程安全?

HashMap 线程不安全的原因主要是:

  • HashMap 内部的 get、put、自动扩容方法都不是原子性的。但有多个读写线程并发执行的情况下,就可能会出现数据不一致或者抛出异常。比如出现元素丢失、元素覆盖等情况。
  • 元素丢失:
    • 一个读线程和一个写线程并发运行。
    • 读线程已经确定了元素在数值中的位置,正要提取元素。
    • 这时写线程运行了,它往 HashMap 中插入了一个元素导致 HashMap 扩容。这导致读线程原本要读的元素在数组中的位置恰好因为扩容转移到了一个新的位置。
    • 这时读线程运行,它还读它之前计算出的那个位置。导致读到的是 null。
  • 元素覆盖:
    • 两个写线程要写两个不同的 key-value 键值对到 HashMap。
    • 假设这两个 key 计算出在数组中的位置相同,也就是发生了哈希冲突。正常情况下应该会把这两个键值对在 HashMap 某个位置以链表的形式都存进 HashMap。
    • 而在并发的情况下,可能第一个线程计算出 key 在数组中的索引,并且该索引位置原本没有元素,所以正要直接往这个位置插入元素。
    • 这个时候第二个线程执行了,它也做出了和线程 1 一样的判断,直接往这个位置插入了元素。
    • 之后线程 1 再执行,由于它之前已经执行过了判断认为此处无元素,所以它会直接把元素插到这个位置中。也就是会覆盖线程 2 插入的元素。
    • 归根结底就是因为 put 方法判断和插入不是原子性的。

如何实现线程安全:

  • 使用并发安全的 concurrentHashMap,采用分段锁实现,允许多个线程同时进行读操作,并发性能高。
  • 使用 Collections.synchronizedMap 保证 HashMap。

concurrentHashMap 如何保证线程安全

  1. jdk 1.7 中,使用数组 + 链表结构。其中数组分为两类。大数组是 Segment 和小数组 HashEntry。ConcurrentHashMap 的线程安全是建立在 Segment 加 ReentrantLock 重入锁来保证。
  2. jdk 1.8 中,使用数组 + 链表 + 红黑树结构。使用 CAS 或 synchronized 来保证线程安全。缩小了锁的粒度,查询性能更高。

HashMap和ConcurrentHashMap的区别?

  1. 线程安全:

    • HashMap 是线程不安全的。当有多个读写线程并发执行的时候,可能会出现数据不一致或者抛出异常的情况。
    • ConcurrentHashMap 是线程安全的。它采用分段数的设计。每个段都有自己的锁。多个线程能同时访问不同的段,提高并发性能。
  2. 性能和使用场景:

    • HashMap 的性能会高于 ConcurrentHashMap,因为 ConcurrentHashMap 会有维护并发安全的开销。
    • 但在多线程环境下,还是建议使用 ConcurrentHashMap。既能保证线程安全又有不错的并发性能;在单线程环境下则使用 HashMap 以获得最佳性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值