zoukankan      html  css  js  c++  java
  • ConcurrentHashMap(1.7)分析

    1.  先来了解ConcurrentHashMap中的几个成员,当然大多数与HashMap中的相似,我们只看独有的成员

    /**
         * The default concurrency level for this table, used when not
         * otherwise specified in a constructor.
         */
        static final int DEFAULT_CONCURRENCY_LEVEL = 16;       //默认的并发级别
    /**
    * The maximum capacity, used if a higher value is implicitly
    * specified by either of the constructors with arguments. MUST
    * be a power of two <= 1<<30 to ensure that entries are indexable
    * using ints.
    */
    static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量

    /**
    * The minimum capacity for per-segment tables. Must be a power
    * of two, at least two to avoid immediate resizing on next use
    * after lazy construction.
    */
    static final int MIN_SEGMENT_TABLE_CAPACITY = 2; //每个Segement中的桶的数量

    /**
    * The maximum number of segments to allow; used to bound
    * constructor arguments. Must be power of two less than 1 << 24.
    */
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative //允许的最大的Segement的数量

    /**
    * Mask value for indexing into segments. The upper bits of a
    * key's hash code are used to choose the segment.
    */
    final int segmentMask; //掩码,用来定位segements数组的位置

    /**
    * Shift value for indexing within segments.
    */
    final int segmentShift;       //偏移量,用来确认hash值的有效位

    /**
    * The segments, each of which is a specialized hash table.
    */
    final Segment<K,V>[] segments; //相当于多个HashMap组成的数组
    
    

      

    static final class Segment<K,V> extends ReentrantLock implements Serializable {  //内部类Segment,继承了ReentrantLock,有锁的功能
           /**
             * The maximum number of times to tryLock in a prescan before
             * possibly blocking on acquire in preparation for a locked
             * segment operation. On multiprocessors, using a bounded
             * number of retries maintains cache acquired while locating
             * nodes.
             */
            static final int MAX_SCAN_RETRIES =
                Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
    
            /**
             * The per-segment table. Elements are accessed via
             * entryAt/setEntryAt providing volatile semantics.
             */
            transient volatile HashEntry<K,V>[] table;             //每个Segement内部都有一个table数组,相当于每个Segement都是一个HashMap
    
          
            transient int count;                            //这些参数与HashMap中的参数功能相同
    
            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;
            }
    
            final V put(K key, int hash, V value, boolean onlyIfAbsent) {  //向Segement中添加一个元素
               
            }
    

    2. 构造函数

      

    @SuppressWarnings("unchecked")
        public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
            if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)  //参数校验
                throw new IllegalArgumentException();
            if (concurrencyLevel > MAX_SEGMENTS)
                concurrencyLevel = MAX_SEGMENTS;
            // Find power-of-two sizes best matching arguments
            int sshift = 0;        
            int ssize = 1;                  //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16 
            while (ssize < concurrencyLevel) {  
                ++sshift;               //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数    
                ssize <<= 1;            
            }
         
    this.segmentShift = 32 - sshift; //segement偏移量 this.segmentMask = ssize - 1; //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;
          
    int c = initialCapacity / ssize; //计算每个Segement中桶的数量 if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = //初始化第一个segement new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; //新建segements数组,并将s0赋值 UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }

    3 . 我们来看put()方法

    @SuppressWarnings("unchecked")
        public V put(K key, V value) {
            Segment<K,V> s;    
            if (value == null)                   //ConcurrentHashMap中value不能为空
                throw new NullPointerException();
            int hash = hash(key);                       //获取到key的hash值
            int j = (hash >>> segmentShift) & segmentMask;   //定位到某个segement位置
            if ((s = (Segment<K,V>)UNSAFE.getObject          //从上面的构造方法中我们知道,segments数组只有0位置的segment被初始化了,因此这里需要去检测计算出的位置的segment是否被初始化
                                          由于是并发容器,所以使用UNSAFE中的方法
    (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false); //将元素插入到定位的Segement中 }
    final V put(K key, int hash, V value, boolean onlyIfAbsent) {   //segement中的put()方法
                HashEntry<K,V> node = tryLock() ? null :   //获取锁,若获取不到锁,则县创建节点并返回
                    scanAndLockForPut(key, hash, value);
                V oldValue;
                try {                               
                    HashEntry<K,V>[] tab = table;          //之后的算法就与HashMap中相似了
                    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;
            }

    4. 来具体看一下SegementMask与SegmentShift这两个变量时怎么使用的?

      

         int sshift = 0;        
            int ssize = 1;                  //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16 
            while (ssize < concurrencyLevel) {  
                ++sshift;               //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数    
                ssize <<= 1;            
            }
         
            this.segmentShift = 32 - sshift;   //segement偏移量
            this.segmentMask = ssize - 1;     //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标

      上面是构造函数中计算这两个变量的代码。

      我们假设concurrencyLevel为默认值16,那么经过计算得到,ssize = 16,sshift = 4,segmentShift  = 28, segementMask = 15

      由于ssize为segements数组的大小,我们可以发现,当 n 与 segmentMask按位与时候正好可以得到<=15的数组,正是segements数组的下标。

       

     int j = (hash >>> segmentShift) & segmentMask;   //定位到某个segement位置

      而segementShift的作用在于缩小hash值的范围,我们并不需要使用hash值所有的位,通过上面的数据,当hash值右移28位后正好可以得到有效计算的位数(4位),因此上面构造函数中的sshift也

      可以表示计算segements数组时的有效位数。

  • 相关阅读:
    20200713 T3 图论
    20200713 T1序列问题
    【题解】P1441 砝码称重
    【题解】P2858 [USACO06FEB]Treats for the Cows G/S
    【比赛】AISing Programming Contest 2019
    20200709 T3 城堡
    20200709 T2 括号
    20200709 T1 笔记
    20200628 T3 网络检查
    个人技术总结
  • 原文地址:https://www.cnblogs.com/liwangcai/p/11881068.html
Copyright © 2011-2022 走看看