HashMap、HashTable、ConcurrentHashMap的区别与联系

本文主要讨论Map家族的区别和联系,个体详细内容单独介绍。

区别:
  1. 线程安全问题:HashTable(修改时锁住整个HashTable)、ConcurrentHashMap(CAS)是线程安全的(),HashMap是线程不安全的;
  2. 性能方面:HashMap > ConcurrentHashMap > HashTable;
  3. HashMap k, v 均支持null,HashTable与ConcurrentHashMap均不支持。
  4. 容量:
HashTableHashMapConcurrentHashMap
初始容量111616
扩容因子0.750.750.75
扩容公式oldSize*2+1oldSize*2oldSize*2
补充:

一. HashMap线程不安全的表现:

  1. put()方法:两个线程put的key,HashCode发生碰撞。在获取了相同的数组下标或父节点,写入时后一个会将前一个的值覆盖,从而造成数据的丢失。
  2. put()时,若++size>threshold则会触发resize()方法进行扩容。resize()会重新排列Map内的entry,多线程的情况下会产生闭合链表(1.8之前采用头插法会导致此结果)导致get的时候出现死循环;
  3. remove()方法:线程一获取删除地址后挂起,线程二删除后,线程一按原地址删除可能会删除错误的目标。

二. (1.8之前)循环链表的产生:
Entry[] table是全局变量,多个线程操纵同一个实例table在多个线程是共享的。

for(Entry<k,v> e : table){
	while(null!=e){
		Entry<k,v> next = e.next;
		if(rehash){
			e.hash = null == e.key ? 0 : hash(e.key);
		}
		int i = indexFor(e.hash, newCapacity);
		//头插法
		e.next = newTable[i];
		newTable[i] = e;
		e = next;
	}
}

在这里插入图片描述
假设有两个线程:线程一和线程二。当线程一执行到Entry<k, v> next = e.next;时资源耗尽,线程二切入并执行完毕。此时由于table为线程一与线程二共享,因此table值变为线程二扩容后的值。
此时e->key3,next->key7;
将key3插入线程一的newTable,key3.next->null,e->key7;
此时经过线程二key7.next->key3,所以next->key3,e.next = newTable[i]导致key7.next->key3;
继续执行e->key3,e.next=newTable[i]也就是key7,就形成了一个闭环。
在这里插入图片描述
会导致死循环使CPU资源耗尽。

三. 为何在多线程的情况下(HashTable、ConcurrentHashMap)不支持null?
当你通过get(k)获取对应的value时,如果获取到的是null时,你无法判断,它是put的时候value为null,还是这个key从来没有做过映射。而HashMap是非并发的,可以通过contains(key)来做这个判断。而支持并发的Map在调用m.contains(key)和m.get(key),m很可能已经被其他线程改变了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值