ConcurrentHashMap分析

ConcurentHashMap分析

JDK7

JDK7中ConcurrentHash内部是由分段锁实现的(segment),每一个segment内部又是由hashEntry的数组组成,hash冲突的话是用链地址方法解决。
1.构造方法

	 @SuppressWarnings("unchecked")
    public ConcurrentHashMap(int initialCapacity,//初始化大小
                             float loadFactor//扩容因子, int concurrencyLevel//并发度(默认16)) {
        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;//表示当前当前segement=1<<sshift
        int ssize = 1; //segement的zise
        while (ssize < concurrencyLevel) {
            //while循环 确保ssize的大小是大于切最接近concurrencyLevel的2的幂方数字
            ++sshift;
            ssize <<= 1;
        }  
        this.segmentShift = 32 - sshift;  //初始化为segmentShift =32-4 =28
        this.segmentMask = ssize - 1;     //15 ,这两个值后续用来做
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //cap为每个segment对应entry大小,默认为2.即一般情况下initialCapacity,
        //若initialCapacity不为2的幂次方的话会进行设置为2的幂次方
        //例如:initialCapacity = 33,ssize=16 c-> 3 ,cap->4,这个时候实际的大小则为4*16.。
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        //默认只初始化第一个segment
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

2.什么是segement?

static final class Segment<K,V> extends ReentrantLock implements Serializable

可以看出segment其实是一个reentrantLock,可重入锁。到这里就可以看出1.7的concurrentHash的并发是基于reentrantLock实现的。
3.put方法

  public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);//经过再hash得到的32位hashcode
        int j = (hash >>> segmentShift) & segmentMask; //默认得到高4位&(2^4-1)确定segment的位置
        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);
    }

//定位segment的位置之后调用segment的put 方法放入元素

 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
           HashEntry<K,V> node = tryLock() ? null :
               scanAndLockForPut(key, hash, value);
               //调用当前segment的tryLock方法,tryLock内部是采用cas实现。若cas state(0,1)成功的话就为true,或者重进入成功的话state+1,若tryLock失败则一直循环tryLock,若是重复次数超过64次的话则直接调用lock方法,阻塞。等待加锁成功      
           V oldValue;
           try {
               HashEntry<K,V>[] tab = table;
               int index = (tab.length - 1) & hash;//定位hash对应的table中处于的位置
               HashEntry<K,V> first = entryAt(tab, index);//取出定位位置的第一个元素
               for (HashEntry<K,V> e = first;;) {//循环遍历链表,然后将node设置在链表的尾部
                   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);//若是长度超过的阈值,进行rehash扩容
                       else
                           setEntryAt(tab, index, node);
                       ++modCount;
                       count = c;
                       oldValue = null;
                       break;
                   }
               }
           } finally {
               unlock();//释放锁。
           }
           return oldValue;
       }

4.rehash方法

 private void rehash(HashEntry<K,V> node) {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1; //长度*2
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity];
            int sizeMask = newCapacity - 1;
            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;//通过hash%长度定位位置
                    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; //感觉这一步没啥用。找出一个通过hash定位不一致的。
                        // 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]);//将当前插入的这个node设置在链表的头部
            newTable[nodeIndex] = node;
            table = newTable;
        }

5,get方法没什么好分析的,就是先hash高位定位segment,然后通过hash&size-1得到具体位置,遍历链表,得到具体的值
6.size()方法

    public int size() {
        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation 
                         //可以看到size方法是自旋调用每一个segment 的lock方法。
                        //可以知道currentHashMap的size()方法是对于整体的性能是有影响的。
                        //在调用size方法会阻塞put方法等需要持有锁的方法
                }
    //省略。。。
    }

JDK8

ConcurrentHashMap在1.8中去除了segment的实现方式,直接更改为node数组的方式
1,构造方法

   public ConcurrentHashMap(int initialCapacity) {
       if (initialCapacity < 0)
           throw new IllegalArgumentException();
       int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                  MAXIMUM_CAPACITY :
                  tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
       this.sizeCtl = cap;
   }
// sizeCtl 的定义: When negative, the table is being initialized or resized:-1 for initialization, 
else -(1 + the number of active resizing threads,
翻译过来就是,当值为负数的时候表示正在初始化或者在扩容,当值为-1的时候表示在初始化,
其他值得表示下一次扩容大小-1,

2.put方法:内部调用putval方法

  final V putVal(K key, V value, boolean onlyIfAbsent) {
       if (key == null || value == null) throw new NullPointerException();
       int hash = spread(key.hashCode());//再hash后的值
       int binCount = 0;
       for (Node<K,V>[] tab = table;;) {
           Node<K,V> f; int n, i, fh;
           if (tab == null || (n = tab.length) == 0)
               tab = initTable();
           else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
               if (casTabAt(tab, i, null,
                            new Node<K,V>(hash, key, value, null))) 
                            //cas操作若是定位的索引上的值为null,
                            //则直接cas操作将当前key,value封装node设置
                   break;                   // no lock when adding to empty bin
           }
           else if ((fh = f.hash) == MOVED)
               tab = helpTransfer(tab, f);//若是hash值为-1的话表示有线程正在进行扩容操作,当前线程帮助数组扩容
           else {
               V oldVal = null;
               //对对应索引位置上面的值进行加锁,成功之后再次验证链头元素是否为当前元素,若等于的话进行插入操作
               synchronized (f) {
                   if (tabAt(tab, i) == f) {
                       if (fh >= 0) {
                           binCount = 1;
                           for (Node<K,V> e = f;; ++binCount) {
                               K ek;
                               if (e.hash == hash &&
                                   ((ek = e.key) == key ||
                                    (ek != null && key.equals(ek)))) {
                                   oldVal = e.val;
                                   if (!onlyIfAbsent)
                                       e.val = value;
                                   break;
                               }
                               Node<K,V> pred = e;
                               if ((e = e.next) == null) {
                                   pred.next = new Node<K,V>(hash, key,
                                                             value, null);
                                   break;
                               }
                           }
                       }
                       //上面的操作是若key的hash值相同且equals相同,则进行替换,二者有一不同,则添加到链表的尾部
                       else if (f instanceof TreeBin) {//若是树的形式的话进行红黑树的添加
                           Node<K,V> p;
                           binCount = 2;
                           if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                          value)) != null) {
                               oldVal = p.val;
                               if (!onlyIfAbsent)
                                   p.val = value;
                           }
                       }
                   }
               }
               if (binCount != 0) {
                   if (binCount >= TREEIFY_THRESHOLD)
                       treeifyBin(tab, i); //若是大于了树化的阈值(8),则将链表转换成为树
                   if (oldVal != null)
                       return oldVal;
                   break;
               }
           }
       }
       addCount(1L, binCount); //binCount表示在索引位置保存了多少个元素,binCount+1
       return null;
   }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值