关于hashmap和hashtable的区别,及如何使hashmap变得线程安全?(除了synchronized)---concurrentHashmap

HashMap 继承自AbstractMap类,底层数组+链表实现,可以存入null键及null值,线程不安全,而效率也比较高,初始容量默认为16,每次扩容会变为原来的2倍;

当map中的元素超过entry数组的75%,触发扩容操作,为了减少链表的长度,元素分配更均匀;

扩容针对整个map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入;

Hashtable 继承自Dictionary类,底层数组+链表实现, 不允许存入null键和null值,线程安全,实现线程安全的方式是在修改数据时,锁住整个表,所以其效率低,默认初始容量为11,每次扩容变为原来的2n+1;

那么为什么hashtable是线程安全的呢?我们可以翻看一下:

例如:put 方法,hashtable中几乎所有的方法都是被synchronized 修饰的;以保证其安全性;

synchronized 可以修饰静态方法(锁对象为本类的字节码文件对象)、修饰普通方法(锁对象为this,即谁调用了它)、修饰某段代码(锁对象为任意对象);

再来说hashmap,它的get put 方法没有加同步,那么意味着,如果2个线程同时put了两个相同的key时,这2个key会被放到数组中的同一位置,(hashmap 中的key是用数组来存储的),就会导致其中一个线程put的数据被覆盖;另外:hashmap并发执行put 操作时会引起死循环(其实是发生在数组扩容时,hashmap 中的node链表形成环形数据结构)。

hashmap基于hash算法,实现对数据的读写,当调用put方法传入键值对时,会调用key的hashcode方法计算出哈希值,并依据此找到对应的bucket位置来存储值对象,如果出现了两个不同的key的hashcode值相同,它们会存储在同一个bucket位置的链表中,然后通过key对象的equals方法来比对 找到对应的键值对,如果链表大小超过阈值,链表就会形成树形结构;

所以在api中给出了这样的方法保证hashmap的安全:

 Map m = Collections.synchronizedMap(new HashMap(...));
当然还有另外一种方法就是:使用ConcurrentHashmap: 底层采用分段的数组+链表实现,线程安全,这个类遵守与Hashtable
相同的功能规范,即支持Hashtable 的所有功能(注意:它 允许将 null 用作键或值);除此之外还支持多线程对map 的读操作,在执行写操作时,CHM只锁住部分的Map,同时支持16个进程进行写操作,读操作不受限制;
原因在于ConcurrentHashmap引用了锁分割,将map分割为16部分(默认初始容量为16),由分段锁控制不同的segment;
Map m = Collections.synchronizedMap(new HashMap(...)); 方法 和hashtable 效率相对较高(应用场景:读取多于写入);

锁分段技术如何保证了线程安全:首先将数据分成一段一段存储,然后给每段数据配一把锁,当一个线程占用锁并访问其中一段的数据时,其他段的数据是可以同时被其他线程访问而不受影响的;chm默认将hash表分为16个bucket,如进行get put remove操作时,只需要锁住当前用到的锁,这样,原来只能一个线程进入,现在却可以同时有16个线程同时访问执行,并发性能显著提升;

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值