ConcurrentHashMap详解

ConcurrentHashMap是jdk1.5之后支持高并发、高吞吐量的线程安全的HashMap实现。Hashtable虽然使用synchronized保证线程安全,但它是锁住整张hash表让单一线程独占,非常影响性能。ConcurrentHashMap也需要通过加锁保证线程安全,但其锁的粒度及如何加锁的方法使其能够支持高并发、高吞吐量并保证线程安全。

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

        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }

如上可见,ConcurrentHashMap读取并发的时候,由于其他都设置为final,只有value设置为volatile可以防止链表被破坏,确保读操作可以看到最新的值而避免加锁。

static final class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        transient volatile int count;
        transient int modCount;
        transient int threshold;
        transient volatile HashEntry<K,V>[] table;
        final float loadFactor;

        Segment(int initialCapacity, float lf) {
            loadFactor = lf;
            setTable(HashEntry.<K,V>newArray(initialCapacity));
        }

Segment里面的成员变量的意义:
count:Segment中元素的数量
modCount:对table的大小造成影响的操作的数量(比如put或者remove操作)
threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容
table:链表数组,数组中的每一个元素代表了一个链表的头部
loadFactor:负载因子,用于确定threshold

ConcurrentHashMap将hash表分为多个段(默认为16个),做修改操作的时候每个线程只需要锁定所用到的段,多个修改操作修改不同的段时,并发性明显提升。通常只有在跨段的操作(size()、containsValue()等)需要按顺序锁住所有段,完成后再按顺序释放所有段。

在迭代的时候,ConcurrentHashMap使用了weakly consistent迭代器。迭代器初始化后,若有线程修改ConcurrentHashMap,会copy一个新的ConcurrentHashMap用于修改,在iterator完成之后再将头指针替换为新的数据使得迭代和修改同时进行。

V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue = null;
                if (e != null) {
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ++modCount;
                        HashEntry<K,V> newFirst = e.next;
                        for (HashEntry<K,V> p = first; p != e; p = p.next)
                            newFirst = new HashEntry<K,V>(p.key, p.hash,
                                                          newFirst, p.value);
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

remove删除操作先定位到段,然后委托给段的remove操作,不同段的remove操作可同时并发进行。两点注意:第一、删除最后一步操作是将count的值减1,必须最后做此操作,否则读取操作可能看不到之前对段所做的结构性修改;第二、remove执行开始将table赋给一个局部变量tab,因为读volatile变量开销很大,编译器也不能对volatile变量的读写做任何优化。

Put操作也是委托给段的put方法,判断是否需要rehash,是否存在同样一个key的结点,如果存在就直接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。

Size()方法是先在没有锁的情况下对所有段大小求和,如果不成功(遍历时有其他线程对已经遍历过的段进行结构性更新),最多执行RETRIES-BEFORE-LOCK次,还不成功就在持有段锁的情况下对所有段大小求和。没有锁的情况下主要是利用segment的modcount进行检测 ,遍历过程中保存每个segment的modcount,遍历完之后再检测每个modcount有没有改变,有改变就重新计算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值