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 s.put(key, hash, value, false);