小谈HashMap与ConcurrentHashMap

HashMap

JDK7:

在JDK7中,HashMap通过数组加链表的形式存储,当元素个数达到阈值,并且数组下标已经存在元素,则会进行扩容,如果数组下标不存在元素,则直接添加,不会扩容。

JDK7中添加元素使用的是头插法,在高并发的环境下可能会导致死链。

新增对象丢失原因:

  • 并发赋值时被覆盖。
  • 扩容中的数据迁移,新增的数据落在了原来的HashMap中,并且所在的哈希槽已经被遍历过。
  • 多个线程同时执行resize方法,每个线程都会创建Entry,最后的赋值中会覆盖其他线程的数据。
  • 迁移丢失。在并发迁移过程中,next被提前设置为null。

JDK8:

在JDK8中,HashMap通过数组加链表或红黑树的节后进行存储。当链表的长度大于等于8(8来自于泊松分布),并且哈希槽的个数不小于64的时候才会进行链表转红黑树,如果链表大于等于8,但是哈希槽小于64,则会执行resize方法进行扩容。当红黑树节点小于等于6的时候,红黑树会退化成链表。

默认容量为16,默认负载因子是0.75,所以当节点个数大于16 * 0.75 = 12时就需要扩容。

JDK8中添加数据采用的尾插法,避免了死链还保证了数据有序性,同时统计了链表的长度,方便树化和链表化。

ConcurrentHashMap

JDK7:

在JDK7中ConcurrentHashMap是由Segment[]数组加上HashEntry[]组成的。底层用的数组加链表。

原理上ConcurrentHashMap采用的是分段锁,其中的Segment继承于ReentrantLock,它的锁粒度比HashTable更细,当一个线程来访问时,只会占用对应的Segment,对其他Segment不想影响。

在进行put时,通过scanAndLockForPut方法获取到锁,首先通过自旋尝试获取锁,当自旋次数达到MAX_SCAN_RETRIES时,会强行上锁获取对象。

get逻辑比较简单,因为Entry的value属性都是通过volatile修饰的,可以保证可见性,所以不需要加锁。

JDK8:

JDK8抛弃了原有的分段锁,使用的是CAS + synchronized

在putVal时,当该table[i]为空时,直接通过cas进行put;当table[i]不为空,并且table[i]的hash值是-1时,表示其他线程正在扩容,调用helpTransfer()帮助一起扩容;当table[i]有值,且没有在扩容时,直接通过synchronized锁住哈希槽进行添加。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值