JDK1.6的ConcurrentHashMap

1、构造函数:

       在构造函数中,主要就是根据ConcurrentHashMap的loadfactor、initialCapacity和concurrencyLevel来初始化这个ConcurrentHashMap。下面分别来介绍这几个参数的意义。

if (c++ > threshold) // ensure capacity
        rehash();
       loadfactor是ConcurrentHashMap.Segment的负载因子。当Segment中元素的数目达到了threshold,就会调用rehash来扩容。Segment中的threshold的大小=Segment中表的大小*loadfactor。loadfactor的默认大小是0.75,代表当元素数目达到表的3/4的时候就会对这个Segment进行扩容。
if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);

       concurrencyLevel是代表这个ConcurrentHashMap支持并发的等级,也就是有多少个Segment。concurrencyLevel的大小总是2的指数次方,并且不会超过1<<16。

if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);

       initialCapacity是代表当前ConcurrentHashMap中的初始大小,并不是每个Segment的大小,initialCapacity不超过1<<30。注意代码中的cap,Segment的大小永远都是2的整数次幂大小,这样能使元素更加平均的分布到segments中。

2、Segment

       Segment是一种分片锁的思想,在介绍ConcurrentHashMap的其他方法之前要先知道Segment的一些属性的作用。

transient volatile int count; //<span style="font-size:12px;">Segment中的元素数目</span>。
        transient int modCount; //Segment中修改元素的次数。
        transient int threshold; //扩容Segment的阈值。
        transient volatile HashEntry<K,V>[] table; //Segment储存元素的地方。
        final float loadFactor; //负载因子。
 

       modCount调用的地方有put、remove、clear。replace则不会调用modCount,因为它没有Segment的表的物理结构。

final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }
根据hash的高位来将该键值对放在不同的Segment。

3、put

       put方法有两个版本,put和putIfAbsent。这两个方法的区别就是当key在ConcurrentHashMap已经存在的时候是否替换value。

V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();
            try {
                int c = count;
                if (c++ > threshold) // ensure capacity
                    rehash();
                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;
                if (e != null) {
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;
                }
                else {
                    oldValue = null;
                    ++modCount;
                    tab[index] = new HashEntry<K,V>(key, hash, first, value);
                    count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }
        }
       在对表进行修改之前,会先加锁。然后判断是否需要对表扩容。接下来根据hash值计算地址取链表头。遍历链表,寻找Key是否已经在表里存在了,如果没有存在则直接从链表头插入,如果已经存在则要根据onlyIfAbsent来判断是否需要替换oldValue。返回oldValue,解锁。

4、rehash

       rehash这个的访问权限是default的,所以只有子类和同包的类才可以调用它。在ConcurrentHashMap中发现只有Segment的put方法才调用了rehash,所以在rehash中并不需要加锁来防止出现并发问题。

void rehash() {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            if (oldCapacity >= MAXIMUM_CAPACITY)
                return;
            HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1);
            threshold = (int)(newTable.length * loadFactor);
            int sizeMask = newTable.length - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                // We need to guarantee that any existing reads of old Map can
                //  proceed. So we cannot yet null out each bin.
                HashEntry<K,V> e = oldTable[i];

                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    int idx = e.hash & sizeMask;

                    //  Single node on list
                    if (next == null)
                        newTable[idx] = e;

                    else {
                        // Reuse trailing consecutive sequence at same slot
                        HashEntry<K,V> lastRun = e;
                        int lastIdx = idx;
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        newTable[lastIdx] = lastRun;

                        // Clone all remaining nodes
                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                            int k = p.hash & sizeMask;
                            HashEntry<K,V> n = newTable[k];
                            newTable[k] = new HashEntry<K,V>(p.key, p.hash,
                                                             n, p.value);
                        }
                    }
                }
            }
            table = newTable;
        }
       获得Segment表的大小oldTable.length,然后乘2判断是否超过1<<30。如果大于直接返回。否则会使用oldTable.length<<1创建新的数组,并且计算新的threshold。接下来遍历oldTable把每个链表中的Entry重新插入到Segment中。因为Segment的大小是2的整数次幂,所以原来位置链表中的Entry只有两个选择,一个是插入在原位curIdx的链表中,一个是插入到oldTable.length+curIdx的链表中。源码中的处理方式,获得i位置上的链表头。如果非空,则判断next是否为空,如果next非空直接插入到对应位置做为链表头。如果next不为空,则需要先遍历一次链表,寻找最后一个idx不同的Entry。这里的优化措施就是第一次遍历链表记录下lastRun,lastRun后面的Entry的idx都和lastRun的idx相同,所以不需要多次插入,只需把lastRun及lastRun后面的节点一次插入到新链表的头后,然后开始重新遍历链表,将剩余的表中Entry插入到链表头中去。

5、get

       get也会交给相应的Segment中去操作。但是get不会对表的结构产生改变,所以不用加锁。但是如果不加锁的话会产生一致性的问题,put到Segment中的Entry,如果并发读可能会访问不到相应的Entry。所以ConcurrentHashMap中的get是弱一致性的(具体方法大家可以参考http://ifeve.com/concurrenthashmap-weakly-consistent/)。

V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K,V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        return readValueUnderLock(e); // recheck
                    }
                    e = e.next;
                }
            }
            return null;
        }
这里的count != 0的注释和put方法中的count=c的注释就解释了这两个操作是hb的,但是无法保证操作的原子性,所以没办法达到强一致性。

6、contain、containsValue

       contain就是调用了containValue,下面上containsValue的源码。

public boolean containsValue(Object value) {
        if (value == null)
            throw new NullPointerException();

        // See explanation of modCount use above

        final Segment<K,V>[] segments = this.segments;
        int[] mc = new int[segments.length];

        // Try a few times without locking
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            int sum = 0;
            int mcsum = 0;
            for (int i = 0; i < segments.length; ++i) {
                int c = segments[i].count;
                mcsum += mc[i] = segments[i].modCount;
                if (segments[i].containsValue(value))
                    return true;
            }
            boolean cleanSweep = true;
            if (mcsum != 0) {
                for (int i = 0; i < segments.length; ++i) {
                    int c = segments[i].count;
                    if (mc[i] != segments[i].modCount) {
                        cleanSweep = false;
                        break;
                    }
                }
            }
            if (cleanSweep)
                return false;
        }
        // Resort to locking all segments
        for (int i = 0; i < segments.length; ++i)
            segments[i].lock();
        boolean found = false;
        try {
            for (int i = 0; i < segments.length; ++i) {
                if (segments[i].containsValue(value)) {
                    found = true;
                    break;
                }
            }
        } finally {
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        return found;
    }

       判断ConcurrentHashMap中是否包含某个value就要在每个Segment去寻找是否包含这个value。首先先创建的mc数组就是记录每个Segment的modCount,然后去执行遍历查找操作,查找成功则会返回true。如果不成功则会再次遍历Segments的modCount去和mc中的每个值去比较,判断是否某个Segment发生了改变。如果发生了改变,cleanSweep被设置为false,则会重新进行。直到每个Segment在这期间都没有被修改过,然后会返回false。int c = segments[i].modCount;保证了可见性。如果每次都发现segments的Entry被修改过,那么重试次数达到RETRIES_BEFORE_LOCK的时候就会继续跳过无锁化的查找,对每个Segment进行加锁,然后再进行查找。

7、isEmpty

public boolean isEmpty() {
        final Segment<K,V>[] segments = this.segments;
        int[] mc = new int[segments.length];
        int mcsum = 0;
        for (int i = 0; i < segments.length; ++i) {
            if (segments[i].count != 0)
                return false;
            else
                mcsum += mc[i] = segments[i].modCount;
        }
        
        if (mcsum != 0) {
            for (int i = 0; i < segments.length; ++i) {
                if (segments[i].count != 0 ||
                    mc[i] != segments[i].modCount)
                    return false;
            }
        }
        return true;
    }

       在isEmpty中还是使用了记录segments的modCount的方式来进行统计,第一遍统计,如果有某个Segment的count不为0则直接返回false,如果统计segments的count都为0,那么需要第二次遍历segments,如果遍历的时候某个Segment的count不为0则直接返回false。如果某个Segment的count为0,并且modCount不为0,说明发生了ABA问题,那么就返回false,因为在isEmpty统计的过程中,segments并不为空。

转载请标明出处:http://blog.csdn.net/hahaha1232     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值