接前面一篇HashMap的文章(http://blog.csdn.net/fanst_/article/details/44986517),简单介绍下ConcurrentHashMap。注:JDK版本为1.7.0_45,其他版本代码有些许差异,但整体结构和逻辑相同。
存储结构
HashMap的存储结构如下图:
ConcurrentHashMap可以看作是上图结构的集合:
final Segment<K,V>[] segments;
每一个Segment中,包含HashEntry的数组,而每一个HashEntry的结构,与上面HashMap相同。
transient volatile HashEntry<K,V>[] table;
增加元素
为什么ConcurrentHashMap比HashMap多了一层数据段的概念呢,这就是它做到线程安全又尽可能减少性能损耗的巧妙之处。我们来看下put方法:
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
// 首先通过一个复杂的计算得到一个hashIndex,利用这个值获取到对应的数据段,该算法应该能做到将数据段获取均匀分布
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
// 未获取到时,创建一个新的Segment,里面比较复杂,暂且不谈。
s = ensureSegment(j);
// 调用Segment的put方法,增加键值对
return s.put(key, hash, value, false);
}
下面为Segment的put方法:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 对Segment加锁
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
// 此处与HashMap中的逻辑相同,利用Hash的Index(key的Hash值和table的长度-1取与值),目的是将不同Hash的key分散到对应的table数组中。
int index = (tab.length - 1) & hash;
// 找到对应table[i]的链表,开始循环寻找key
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
// 已经包含了key值,那么将value用新值覆盖
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
// 未找到对应的Entry链表,则创建一个出来。
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
// 总数超过threshold,扩展table大小为当前的2倍,注意这个操作也是在lock里面进行的,因此ConcurrentHashMap在并发时不会出现HashMap的死循环现象。
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
// 解锁
unlock();
}
return oldValue;
}
加锁的机制
我们看到,ConcurrentHashMap加锁使用的是ReentrantLock类,Segment继承了ReentrantLock:
static final class Segment<K,V> extends ReentrantLock implements Serializable
Segment类的put方法在开头和结尾分别进行加锁和释放锁的操作:
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
finally {
unlock();
}
下面我们分析下ReenTrantLock如何加锁的。
tryLock()逻辑分析如下:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程,线程安全体现在不同线程操作同一个Segment时的处理逻辑中。
final Thread current = Thread.currentThread();
// 获取锁状态,c的值大于0说明有其他线程已经加锁
int c = getState();
// c==0,未加锁
if (c == 0) {
// 使用同步方式判断当前状态确实为0,那么将state更新为acquires(值为1),jdk7用到了native方法,不进行详细描述了。
if (compareAndSetState(0, acquires)) {
// 当前线程加锁成功,将当前线程赋值给exclusiveOwnerThread
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程已经是exclusiveOwnerThread,说明当前线程在重复加锁,那么也认为锁定成功
else if (current == getExclusiveOwnerThread()) {
// state值需要加1
int nextc = c + acquires;
// overflow,超出int
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 更新state
setState(nextc);
return true;
}
// 其他情况返回加锁失败
return false;
}
tryLock()方法返回false,说明Segment已经被其他线程锁定使用,这时调用scanAndLockForPut()
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 通过hash尝试寻找目标Entry对应的链表第一个元素
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
// 循环尝试锁定Segment,这个循环最终必然会加锁成功
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
// 目标Entry的链表在在Segment中不存在时,及当前put的是该hash的第一个元素,则创建一个新的HashEntry
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
// 链表已存在了,循环链表寻找相同key的元素,找到后,设置retries为0,走出当前if
else if (key.equals(e.key))
retries = 0;
else
// 循环链表寻找key相同元素
e = e.next;
}
// 当重复锁定次数大于最大次数,直接锁定,注意,这个lock()方法内部逻辑分析见下面代码块
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
// 判断链表的first是否被修改(例如在本方法执行期间其他线程扩展了Segment中的table大小,导致链表反转)
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
lock()方法:
public void lock() {
sync.lock();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// compareAndSetState使用native的线程安全方法,判断当前state为0时,设置为1
// 如果该方法返回成功,则当前线程加锁成功。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 仍旧被其他线程加锁
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
public final void acquire(int arg) {
// 直接加锁失败,会循环反复尝试加锁直到成功锁定
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断当前线程,这个方法
selfInterrupt();
}