HashMap 、ConcurrentHashMap的区别

Hashmap的key和value都可以为null, 是非线程安全的,存储的是键值对;

hashmap是基于哈希的原理,put存入键值对时,会对键调用 hashCode()方法,返回的hashcode用于找到bucket位置来存储Entry对象,hashMap是在bucket中储存键对象和值对象。当两个对象的hashcode相同时,代表他们的bucket位置相同,此时会发生“碰撞”。因为hashmap 是使用链表存储对象,所以这个对象会存储在链表中。那么两个对象的hashcode相同时,get方法获取对象是如何操作的呢?会根据键值找到bucket存储位置,在遍历链表调用keys.equals()找到链表中正确的节点,最终找到要找的值对象。(这里能够遍历获取正确的值是因为链表中存储的是键值对)

通过采用合适的equal()和hashcode()将会减少碰撞的发生,提高效率。

hashmap默认的负载因子大小是0.75,即当一个map填满了75%的bucket时,和其他集合(如Arraylist)一样,将会创建原来hashmap大小的两倍的bucket数组,来重新调整map的大小。并将原来的对象放入新的bucket数组中。在重新调整hashmap大小中存在哪些问题呢?在多线程条件下会存在条件竞争。因为如果两个线程都发现HashMap需要重新调整大小了,他们就会同时试着调整大小。在调整大小的过程中,存储在链表中的元素次序会反过来,因为移动到新的bucket位置时,hashmap并不会将元素放在链表的尾部,而是放在头部,如果条件竞争了,就会死循环。所以多线程慎用hashmap。所以concurrentHashmap应运而生。

接下来研究:

1.hashmap并发情况put 时,为啥会导致死循环?

2.concurrentHashMap如何实现了线程安全?

1. 如有hashmap数据结构如

《HashMap 、ConcurrentHashMap的区别》

1)线程A和线程B都要执行put,线程A 执行到一半挂起,但是记录到 e=3 next = 7

2)线程B在自己线程中完成将旧表变成新表,然后写入到了内存中

《HashMap 、ConcurrentHashMap的区别》

3): 线程A解挂,接着执行(看到的仍是旧表),即从transfer代码(1)处接着执行,当前的 e = 3, next = 7, 上面已经描述。

1. 处理元素 3 , 将 3 放入 线程A自己栈的新table中(新table是处于线程A自己栈中,是线程私有的,不肥线程2的影响),处理3后的图如下:

《HashMap 、ConcurrentHashMap的区别》

2.  线程A再复制元素 7 ,当前 e = 7 ,而next值由于线程 B 修改了它的引用,所以next 为 3 ,处理后的新表如下图

《HashMap 、ConcurrentHashMap的区别》

3. 由于上面取到的next = 3, 接着while循环,即当前处理的结点为3, next就为null ,退出while循环,执行完while循环后,新表中的内容如下图:

《HashMap 、ConcurrentHashMap的区别》

4. 当操作完成,执行查找时,会陷入死循环!

ConcurrentHashMap的原理分析:

concurrentHashMap的目标是实现高并发,高吞吐量的hashmap;所以不能只是单单对Hashmap加锁,因为这样效率实在太低,在ConcurrentHashMap中,数据的组织结构和Hashmap是有所区别的;

一个ConcurrentHashMap由多个Segment组成,每一个Segment都包含了一个HashEntry数组的hashtable,每个Segment包含了自己的hashtable的操作,比如get put replace;这些操作发生的时候,对自己的hashtable进行锁定,由于每一个Segment写操作只锁定自己的hashtable, 所以可能存在多个线程同时写的情况,性能五一好于只有一个hashtable锁定的情况。

    原文作者:h下雪天
    原文地址: https://www.jianshu.com/p/bf5926b6dcde
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞