ConcurrentHashMap简析

接前面一篇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();
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值