关闭

java之ConcurrentHashMap

标签: javaConcurrentHashMap
623人阅读 评论(0) 收藏 举报
分类:

注:代码摘自jdk1.7

ConcurrentHashMap是java.util.concurrent中实现了ConcurrentMap接口的一个线程安全的HashMap,功能和hashtable类似,但是实现原理却不一样。

Hashtable原理很简单,就是利用synchronized去保证线程安全,所以并发量很低。

而ConcurrentHashMap的实现原理,就是把一个hashmap分成若干个小的hashmap,每个小的hashmap有一把锁,这样,当对不同小hashmap操作的时候,就可以并发的进行了。当然,这样做也会带来一些问题,怎么判断ConcurrentHashMap的size,是否为空等。

ConcurrentHashMap中,分成的小hashmap是用一个内部类Segment,segment继承了ReentrantLock。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
而整个ConcurrentHashMap的结构如下图(网上找的)


其实从这张图就大概知道ConcurrentHashMap的工作原理,无非就是put的时候,先hash一次,找出是哪个Segment,然后再在Segment中,找出是哪个桶。get和remove也类似。

但是里面有很多细节的地方。

首先看下put函数

@SuppressWarnings("unchecked")
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);
}
中间那段(有位移操作的两行)可以理解为怎么通过hash值安全的取出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<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;
                    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;
}


第一行,首先是尝试获得锁tryLock(),如果没有获得,那么调用scanAndLockForPut()方法。

1.找出当前hash值对应的桶的第一个节点,然后遍历hash值和equals方法看是否存在当前key值。如果有的话,就改变对应的value值

2.如果没有找出key,则创建node,插入。

3.最后释放锁

这里的modCount是对Segment每进行一次操作(插入,删除)就会++,  而count则是记录当前Segment是否为空。一看就知道,肯定是为了size()和isEmpty服务的。

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
    int retries = -1; // negative while locating node
    while (!tryLock()) {
        HashEntry<K,V> f; // to recheck first below
        if (retries < 0) {
            if (e == null) {
                if (node == null) // speculatively create node
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            else if (key.equals(e.key))
                retries = 0;
            else
                e = e.next;
        }
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}


scanAndLockForPut函数,在一定时间范围内,先不去竞争锁(因为竞争锁就有可能阻塞,阻塞就会影响效率),在这个时间范围内,也不闲着,就看当前Segment中,有没有包含当前key。有的话,我就先创建一个node,并返回。当然,期间还要不断检测Segment是否有变化。

remove和get方法和put的思想类似,就不在说明。

在看看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) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                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)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

重点是for循环里面,首先是不用加锁来统计size,也就是每个Segment里面的count,但是,你每次遍历的时候,可以另外的线程在写或者删元素。只要满足2次遍历,modCount没有变,那么就认为,在第二次循环的时候,没有线程在改变Segment的size。但如果在一定时间,没有通过不加锁的方式获得size,那么就只有先把每个获取每个Segment的锁,在统计size。

isEmpty的方法思想和size类似。


ConcurrentHashMap和HashMap的区别。

最直观的当然是一个线程安全一个不安全。

但是还有就是ConcurrentHashMap不运行null的key。

ConcurrentHashMap有HashMap没有的方法

//如果存在key,则返回key对应的value,如果不存在,则插入key—value
V putIfAbsent(K key, V value);
//当key和value都一样,才删除
boolean remove(Object key, Object value);
//当key和value都一样,才替换
boolean replace(K key, V oldValue, V newValue);
//把当前key的value改为value,如果不存在key返回null
<pre name="code" class="cpp">boolean replace(K key, V value);






0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:52184次
    • 积分:1175
    • 等级:
    • 排名:千里之外
    • 原创:66篇
    • 转载:10篇
    • 译文:0篇
    • 评论:10条
    最新评论