JDK5 提供了多种并发容器类来改进同步容器类的性能。同步容器类将所有对容器的访问操作全部串行化了来实现线程安全,这种方式的代价是严重降低的并发性。当多个线程竞争容器锁时,吞吐量严重降低。
并发容器类的设计是专门针对多线程并发访问设计的。JDK5中新增了ConcurrentHashMap来代替同步且基于散列的Map以及CopyOnWriteArrayList。
ConcurrentHashMap原理介绍
与HashMap一样,ConcurrentHashMap也是一个基于散列的Map,但是确是使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap不是通过给每个方法都加上同一把锁来实现同步使得每次只能有一个线程访问,而是使用一种粒度更加细的加锁机制来实现更大层度的共享,这种加锁机制称为:分段锁。
关于分段锁们可以看下面的篇博文:
分段锁
通过对分段锁的了解我们可以知道:任意数量的读线程可以并发访问Map,读写线程可以并发的访问Map,并且一定数量的写线程也可以并发的访问map.这样在并发环境下大大提高吞吐量,在单线程环境下也只损失很小的性能。
当然对于一些需要在整个Map上进行计算的方法,比如size和isEmpty方法,可能不是完全准确的值,而是稍微过期的数据,不过对于高并发情况下,这种不完全正确性是可以容忍的。
与HashTable,SynchronizedMap相比,ConcurrentHashMap有更大优势更小劣势,所以一般在并发环境中都使用ConcurrentHashMap,除非一些必须以独占方式访问的情况下才考虑SynchronizedMap这种独占锁机制。
上面说了这么多也没涉及到源码层次,都是范范而谈,下面我们从源码的实现角度来分析上面的实现原理
ConcurrentHashMap源码解析
1. 先看类的继承结构
2. 重要属性
查看源码分析重要属性:
/**table的默认的初始容量**/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**默认的加载因子*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**默认的并发水平*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**table限制的最大容量*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/***/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
/***/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/***/
static final int RETRIES_BEFORE_LOCK = 2;
3.构造器
下面给出了4个多态的构造器,最后都会调用第4个构造器。
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//校验三个参数的范围
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//并发锁的个数最多不能超过MAX_SEGMENTS
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
//初始容量限制
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
对于这个构造器,默认情况下是设置初始容量是16,默认加载因子是0.75f,默认并发水平是16级别。当然可以通过构造器设置这三个值。
上面的构造函数主要干了两件事:
1、参数的有效性检查
2、table初始化的长度(如果不指定默认情况下为16)。