ConcurrentHashMap 是Java并发包中提供的一个线程安全且高效的HashMap实现
HashMap的缺点:
多线程环境下HashMap会有线程安全问题,扩容可能会造成环形链表,使cpu空转达到100%,但是HashTable可以保证线程安全
HashTable缺点:
底层使用synchronized锁保证线程安全问题,但是将整个数组锁住了,最终只有一个线程能够调用get或者是put方法,如果没有获取锁的线程就会变为阻塞状态,效率非常低。
多线程环境下建议使用ConcurrentHashMap集合
ConcurrentHashMap1.7源码解读#
ConcurrentHashMap相当于把一个大的ConcurrentHashMap集合拆分成16个HashTable类,每次存放先要计算存放在哪个HashTable里面,然后还要计算存放在HashTable里面的哪个HashEntry<K,V>里面,相当于把锁的粒度进行拆分了,把大锁拆分成小锁。
核心构造参数分析
//初始的容量
private static final int DEFAULT_CAPACITY = 16;
//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//HashEntry table的扩容因子
private static final float LOAD_FACTOR = 0.75f;
// 默认的并发度
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//最小的segment数量
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//最大的segment数量
static final int MAX_SEGMENTS = 1 << 16;
初始化ConcurrentHashMap#
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
//验证参数
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//并发级别大于并发值的话
if (concurrencyLevel > MAX_SEGMENTS)
//最大为
concurrencyLevel = MAX_SEGMENTS;
//sshift=ssize平方的次 =4
int sshift = 0;
//ssize=segments数组的容量=16
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift; //32-4=28 计算index
segmentMask = ssize - 1; //16-1=15 与运算的时候均匀的存放到Segment对象中
this.segments = Segment.newArray(ssize);
//数组容量最大不能大于2的三十次幂
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//HashEntry 的初始容量 16/16=1
int c = initialCapacity / ssize;
//如果1*16<16
if (c * ssize < initialCapacity)
++c;
//HashEntry table默认大小为2
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
//创建第一个Segment,并放入Segment[]数组中,作为第一个Segment
Segment<K,V> s0 =new Segment<K,V>(loadFactor, (int)(cap * loadFactor),(HashEntry<K,V>[])new HashEntry[cap]);
//初始化16大小的Segment
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
//利用cas把s0放入ss中 ss[0]=s0
UNSAFE.putOrderedObject(ss, SBASE, s0);
//赋值给全局segments
this.segments = ss;
}
这里在构造函数初始化S0,目的就是方便其他的key落到不同的Segment中,能够知道创建的Segment的参数,直接采用这个参数
put方法#
public V put(K key, V value) {
Segment<K,V> s;
//参数判断
if (value == null)
throw new NullPointerException();
//这里对key求hash值,并确定应该放到segment数组的索引位置
int hash = hash(key);
//j为索引位置 segmentShift=28 segmentMask=15 hash >>> segmentShift保留最高位4位
int j = (hash >>> segmentShift) & segmentMask;
//判断索引下是否有Segment对象,没有帮忙创建 s=Segment[j]=null
if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null)
//创建一个Segment对象
s = ensureSegment(j);
//这里很关键,找到了对应的Segment,则把元素放到Segment中去
return