并发集合之ConcurrentHashMap(jdk1.7)

知道HashTable是通过synchronized将整个表锁定,线程独占。而ConcurrentHashMap内部使用Segment表示Hash表的不同部分,每个Segment相当于一个HashTable,HashEntry[]构成,对不同Segment的修改可以并发进行。但有些方法是需要锁定整张表的,比如size(),contiansValue()。

1,ConcurrntHashMap初始设置为:

默认初始容量为16,最大容量1<<30

默认负载因子为0.75,

concurrentHashMap由Segment<K,V>数组组成,默认初始数量为16,最大为1<<16

2,Segment继承自ReentrantLock:

数据成员如下:

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    static final int MAX_SCAN_RETRIES =
        Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
    transient volatile HashEntry<K,V>[] table;   
    transient int count;
    transient int modCount;
    transient int threshold;
    final float loadFactor;
    Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
        this.loadFactor = lf;
        this.threshold = threshold;
        this.table = tab;
    }
}
3,HashEntry数据成员:

static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
}

ConcurrentHashMap的remove和put操作都是交给对应的Segment的方法去做:

删除操作:

public V remove(Object key) {
    int hash = hash(key);
    Segment<K,V> s = segmentForHash(hash);  //定位对应的Segment
    return s == null ? null : s.remove(key, hash, null);  //让找到的Segment去做
}
先根据key定位Segment,然后委托给Segment的remove操作,只要remove在不同段操作,可并发进行。下面是Segment的remove方法:

/**
 * Remove; match on key only if value null, else match both.
 */
final V remove(Object key, int hash, Object value) {
    if (!tryLock())
        scanAndLock(key, hash);
    V oldValue = null;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;  //根据hash值找到HashEntry下标
        HashEntry<K,V> e = entryAt(tab, index);
        HashEntry<K,V> pred = null;
        while (e != null) {
            K k;
            HashEntry<K,V> next = e.next;
            if ((k = e.key) == key ||
                (e.hash == hash && key.equals(k))) {//找到key对应的
                V v = e.value;
                if (value == null || value == v || value.equals(v)) {  //比较value是否相等
                    if (pred == null)
                        setEntryAt(tab, index, next);//找到的是头结点,设置next为头结点
                    else
                        pred.setNext(next);  //删除找到的结点
                    ++modCount;
                    --count;
                    oldValue = v;
                }
                break;
            }
            pred = e;
            e = next;
        }
    } finally {
        unlock();
    }
    return oldValue;
}
put方法:

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    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
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

找到对应的Segment,以下是Segment的put方法:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;  //定位到对应HashEntry的索引
        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))) {  key相等或者(hash相等,值相等)
                    oldValue = e.value;   //覆盖之前的值
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);//作为头结点
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

上面说size()锁定全部表,以下是size()源码:

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            if (retries++ == RETRIES_BEFORE_LOCK) {  //尝试几次后没有成功,锁定map
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock();  //所有的Segment被锁定
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)  //加上每个Segment的元素数量count
                        overflow = true;
                }
            }
            if (sum == last)//如果跟上一次的modeCount总数相同,说明求size中间map没有修改,结果有效
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();  //所有的Segment解锁
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

先采用不加锁的方式,连续计算元素的个数,最多计算3次:

1、如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
2、如果前后两次计算结果都不同,则给每个Segment进行加锁,再计算一次元素的个数;
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值