java7 concurrentHashMap

来源:互联网 发布:2007年网络歌曲 编辑:程序博客网 时间:2024/05/29 09:05

一张简易图


java7 concurrentHashMap使用分段锁来提高效率,非常有学习意义,
比起全部使用同步方法的hashTable效率要高很多
上图的结构,外层是一个segment的数组,segment是一个继承ReentrantLock的类,其内部又有一个
hashEntry的数组,每个hashEntry节点有一个next节点,类似于hashMap的结构。


多线程操作数据时候,外层使用CAS确保数据一致性,内层使用Lock或CAS确保数据一致性。

//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);//(j << SSHIFT) + SBASE)) 计算在外层数组的位置//UNSAFE.getObject  使用CAS取得指定位置的值,找到对应的segment//为null  ensureSegment(j)创建一个//使用对应的segment的put方法        return s.put(key, hash, value, false);    }private Segment<K,V> ensureSegment(int k) {        final Segment<K,V>[] ss = this.segments;//volatile类型,数据一致性        long u = (k << SSHIFT) + SBASE; // raw offset        Segment<K,V> seg;        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {//重新检查            Segment<K,V> proto = ss[0]; // use segment 0 as prototype使用第一个segment的相关设置,初始化方法只            int cap = proto.table.length;//初始了第一个segment            float lf = proto.loadFactor;            int threshold = (int)(cap * lf);            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))                == null) { // recheck                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))//使用CAS校验、设置数据,                       == null) {                                       //死循环把new出来的放到指定数组位置                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))                        break;                }            }        }        return seg;    }//segment的putfinal 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;                    }//遍历链表,如果key相等,就更新value,返回//如果执行到最末尾,都没有符合的,执行下面的,把新的node的next指向//first,然后CAS把新的node放在数组索引处,也就是在链表头加上新元素//中间如果需要扩容,则执行rehash扩容方法                    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;        }private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {            HashEntry<K,V> first = entryForHash(this, hash);//CAS找到hashEntry数组指定位置的节点,链的第一个            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;                }//小于0执行的结果是,retries=0//node == null 新的key在指定索引的链上//node != null new出来的,链上没有或者没有链(即指定索引处还没有一个元素)                else if (++retries > MAX_SCAN_RETRIES) {//先自增  再比较  大于一个指定值时候                    lock();//阻塞获取锁,获取到了则返回                    break;                }                else if ((retries & 1) == 0 &&                         (f = entryForHash(this, hash)) != first) {//偶数时候检验,hashEntry数组                    e = first = f; // re-traverse if entry changed 是不是变化了,因为这个时候并没有                    retries = -1;  //获得锁,其他线程可能执行了rehash方法,如果改变了重新执行本方法                }            }            return node;        }//rehash方法private void rehash(HashEntry<K,V> node) {            /*             * Reclassify nodes in each list to new table.  Because we             * are using power-of-two expansion, the elements from             * each bin must either stay at same index, or move with a             * power of two offset. We eliminate unnecessary node             * creation by catching cases where old nodes can be             * reused because their next fields won't change.             * Statistically, at the default threshold, only about             * one-sixth of them need cloning when a table             * doubles. The nodes they replace will be garbage             * collectable as soon as they are no longer referenced by             * any reader thread that may be in the midst of             * concurrently traversing table. Entry accesses use plain             * array indexing because they are followed by volatile             * table write.             */            HashEntry<K,V>[] oldTable = table;            int oldCapacity = oldTable.length;            int newCapacity = oldCapacity << 1;//扩容两倍            threshold = (int)(newCapacity * loadFactor);            HashEntry<K,V>[] newTable =                (HashEntry<K,V>[]) new HashEntry[newCapacity];            int sizeMask = newCapacity - 1;//扩容过程://循环旧的所有hashEntry数组的节点,每个节点执行//如果这个节点不是null,且next==null,那么这条链只有一个,直接复制到新数组//若next!=null,则先循环一次找到最后一个重新计算后的索引不等于第一个元素的索引的元素//那么这个元素后面的元素重新计算索引后都等于这个元素索引,也就是后面的元素重新计算后//也还在一条链上//然后放置上面找到的元素//遍历第一个到上面找到的元素中间的,依次放到新数组中//其实这个过程,与直接全部循环放置相比,可能提高了一下性能,java8优化了这个过程            for (int i = 0; i < oldCapacity ; i++) {                HashEntry<K,V> e = oldTable[i];                if (e != null) {                    HashEntry<K,V> next = e.next;                    int idx = e.hash & sizeMask;                    if (next == null)   //  Single node on list                        newTable[idx] = e;                    else { // Reuse 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 remaining nodes                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {                            V v = p.value;                            int h = p.hash;                            int k = h & sizeMask;                            HashEntry<K,V> n = newTable[k];                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);                        }                    }                }            }            int nodeIndex = node.hash & sizeMask; // add the new node            node.setNext(newTable[nodeIndex]);            newTable[nodeIndex] = node;            table = newTable;        }


//get过程public V get(Object key) {        Segment<K,V> s; // manually integrate access methods to reduce overhead        HashEntry<K,V>[] tab;        int h = hash(key);        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;    //CAS取得指定索引的segment,然后取出segment的hashEntry数组//CAS取得计算后hashEntry数组索引处的元素//链表遍历,比较key,找到则返回        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&            (tab = s.table) != null) {            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);                 e != null; e = e.next) {                K k;                if ((k = e.key) == key || (e.hash == h && key.equals(k)))                    return e.value;            }        }        return null;    }

还是有些不解的地方,比如scanAndLockForPut为什么是偶数次检验,scanAndLockForPut得到Node在不在链上意义是什么,
因为后面的put并没有使用

0 0