目录
1.HashMap的数据结构
2.put()流程
2.1了解扩容机制
2.2扩容三个地方
2.3存入数据步骤
3.HashMap和Hashtable的区别
4.HashMap线程安全的解决办法
4.1Hashtable
4.2Collections
4.3ConcurrentHashMap
线程安全实现机制
1.HashMap的数据结构
从JAVA1.8开始,当链表长度达到8 && 数组容量达到64,则链表转化为红黑树
数组:存储key经过hashcode()计算出的hash值
链表/红黑树:通过线性结构或树状结构存储hash值相同的value
2.put()流程
2.1了解扩容机制
扩容,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。
2.2扩容三个地方
1. 如果数组为空,则进行首次扩容。
2. 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。
3. 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。
2.3存入数据步骤
默认已经算好key的hashcode
1. 判断数组,若发现数组为空,则进行首次扩容。
2. 判断头节点,若发现头节点为空,则新建链表节点,存入数组。
3. 判断头节点,若发现头节点非空,则将元素插入槽内。
4. 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。
3.HashMap和Hashtable的区别
1、 Hashtable在实现Map接口时保证了线程安全性,而HashMap则是非线程安全的。
2、 Hashtable的性能不如HashMap,因为为了保证线程安全它牺牲了一些性能。
3、 Hashtable不允许存入null,而HashMap是允许存入null的。
总结:Hashtable是线程安全的,但仍然不建议在多线程环境下使用Hashtable。因为它是一个古老的API,从Java 1.0开始就出现了,它的同步方案还不成熟,性能不好。
建议使用ConcurrentHashMap
4.HashMap线程安全的解决办法
4.1Hashtable
很古老的API,官方不推荐使用,所以这里参考上文,不多赘述
4.2Collections
Collections类中提供了synchronizedMap()方法,可以将我们传入的Map包装成线程同步的Map。
Collections提供了三类方法来返回一个不可变的集合,这三类方法的参数是原有的集合对象,返回值是该集合的“只读”Map,分别是emptyMap(), singletonMap(),unmodifiableMap()
4.3ConcurrentHashMap
底层数据结构:与HashMap一致,“数组+链表+红黑树”
线程安全方面:采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。
线程安全实现机制
1. 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换。 2. 插入数据时会进行加锁处理,锁定头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。
3. 扩容时会进行加锁,锁定的仍然是头节点。并且,支持多个线程并发对数组扩容,提高并发能力。每个线程需先以CAS操作抢任务。抢到任务后,该线程会锁定槽内的头节点,然后将链表或树中的数据迁移到新的数组里。
4. 查找数据时并不会加锁,所以性能很好。