HashMap基本概念
- 非线程安全
- 支持存储null的key和value,为null的key只有一个,为null的value可以多个
- 默认容量为16,每次扩容为之前的两倍、
- java1.8之前都是数组+链表,在java1.8之后,当解决哈希冲突的时候,当链表长度大于阈值(默认8),链表变为红黑树(需要保证数组长度大于64,否则先扩容)
HashMap的线程问题
首先我们需要知道HashMap放置value的逻辑,通过key的hashcode经过扰动函数处理的到hash值,然后通过(n-1)&hash判断当前元素存放的位置(n为数组的长度),如果当前位置存在元素,判断加入元素的hash值和该元素以及key是否相同,如果相同直接覆盖,不同通过拉链法解决冲突。
//java 1.8
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^:按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在java1.7之前,多线程下,当一个节点有多个元素需要进行扩容,多个线程对链表进行操作,因为采用的头插法,所以可能导致链表中的节点指向错误,比如链表变为环状,此时查询操作就会陷入死循环。
而java1.8采用尾插法解决环状问题,当时多线程下还是可能存在数据覆盖的问题,数据覆盖就是两个线程hash位置一样,某一个线程插入的值被另一个线程覆盖掉了,因此多线程下往往使用ConcurrentHashMap。
ConcurrentHashMap
jdk1.7的时候对整个桶数组进行分割分段(分割锁),每一把锁锁住segment容器中的一部分数组,这样提高并发访问率。
jdk1.8的时候不用segment直接用数组+链表+红黑树(和jdk1.8中的hashmap一样),采用sysnchronized+cas来操作。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。