zoukankan      html  css  js  c++  java
  • jdk1.8 ConcurrentHashMap 源码学习

    上次讲到HashMap,但是HashMap并不是线程安全的,那么有哪些线程安全的Map或者是实现线程安全的map呢?

    1、 HashTable(已弃用),使用的是内置对象锁对map进行同步,并发执行的效率比较低(key和value均不能为null,因为这是用在多线程的,当get返回null时,无法确定是不包含这个key还是值为null,hashMap允许key为null,因为运行在单线程,可以通过containKey来判断是否存在key,而containKey在多线程中可能刚好在你调用之前remove了当前key导致当前key为null);

    2、 ConcurrentHashMap(流行使用),JDK1.7使用分段锁,JDK1.8使用的是CAS+synchronized实现并发访问(key和value均不能为null);

    3、使用Collections的synchronizedMap(Map m) 进行包装,使用的是传入m的内置锁,同样并发执行效率低。

    下面记录一下JDK1.8的ConcurrentHashMap的源码学习,花了挺长时间,比hashmap难多了

    属性

      下面只列出了一些属性

    // 保存键值对总数
    private transient volatile long baseCount;
    
    /* 默认为0,用来控制table的初始化和扩容操作, 小于0时代表正在扩容,并且 -n表示
    * 有n – 1个线程在扩容,正数代表table容量
    */
    private transient volatile int sizeCtl;
    static final int MOVED     = -1; // hash for forwarding nodes,forwarding nodes 是扩容时用到的node
    static final int TREEBIN   = -2; // hash for roots of trees
    static final int RESERVED  = -3; // hash for transient 

    构造方法

    // 带容量的初始化,只是初始化了容量,并没有建立桶数组
    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;
    }

    这里只列出了带容量的构造方法,还有其他的没列出了。

    put()方法

    public V put(K key, V value) {
            return putVal(key, value, false);
    }
    // onlyIfAbsent 为 true的话表示若当前key-value不存在,进行插入;若存在,不对当前存在key-value进行更新
    final V putVal(K key, V value, boolean onlyIfAbsent) {
            // key 和value不能为null
            if (key == null || value == null) throw new NullPointerException();
                //计算key的哈希值
            int hash = spread(key.hashCode());
            int binCount = 0;
            for (Node<K,V>[] tab = table;;) {
                Node<K,V> f; int n, i, fh;
                if (tab == null || (n = tab.length) == 0)
                        // 初始化表数组(或者叫桶数组,默认容量为16),和hashMap一样,table是延迟加载的,initTable()通过CAS机制实现同步,稍后会讲(看到这可以先到后面看看initTable())
                    tab = initTable();
                        
                else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {                    
                  // 通过CAS机制进行并发put,
                  /*
                   *param1 :桶数组
                   *param2 :节点(node)位置偏移
                   *param3 :节点当前预期内存值
                   *param4 :要在当前节点内更新的值
                  */
                    if (casTabAt(tab, i, null,
                                 new Node<K,V>(hash, key, value, null)))
                        break;                   // no lock when adding to empty bin
                }
                    // 常量 MOVED = -1,表示正在扩容
                else if ((fh = f.hash) == MOVED)
           //内置锁是加在每个桶上的,扩容实际上是对每个桶上的元素重新分配桶,扩容可以在不同的桶上多线程并发执行
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
            // 对桶 f 进行同步put操作
                    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;
                                    }
                                }
                            }
                                // f < 0 
                            else if (f instanceof TreeBin) {
                                Node<K,V> p;
                           // 赋值为2的意义只是为了不等于0?不太清楚,addCount(1L, binCount)看到如果在桶数组不为空时,binCount <= 1会直接返回,是这个原因?
                                binCount = 2;
                   // 红黑树节点,加入红黑树中
                                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                               value)) != null) {
                                    oldVal = p.val;
                                    if (!onlyIfAbsent)
                                        p.val = value;
                                }
                            }
                        }
                    }
                    if (binCount != 0) {
                            // TREEIFY_THRESHOLD=8,树化阈值
                        if (binCount >= TREEIFY_THRESHOLD)
                            treeifyBin(tab, i);
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            // 数量+1,并进行是否扩容判断
            addCount(1L, binCount);
            return null;
        }
    View Code

    initTable() 方法

    private final Node<K,V>[] initTable() {
            Node<K,V>[] tab; int sc;
            while ((tab = table) == null || tab.length == 0) {
                if ((sc = sizeCtl) < 0) // 有线程在初始化,当前线程让步
                    Thread.yield(); // lost initialization race; just spin
                else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                  //若CAS成功 把当前对象SIZECTL偏移位置修改为-1,即sizeConrol = -1
                    try {
                        if ((tab = table) == null || tab.length == 0) {
                            int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = tab = nt;
                // 用减法来替代 *0.75,计算机中乘法的操作比减法耗时
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                    break;
                }
            }
            return tab;
        }
    View Code

    addCount()方法

    private final void addCount(long x, int check) {
         // counterCells 在 currentMap 初始化和 put 过程都没有进行初始化,本人暂时也不知道这是用来干嘛的,doc注释为Table of counter cells. When non-null, size is a power of 2.
            CounterCell[] as; long b, s;
            // counterCells为null,不进入if语句
        if ((as = counterCells) != null ||
                !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
                CounterCell a; long v; int m;
                boolean uncontended = true;
                if (as == null || (m = as.length - 1) < 0 ||
                    (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                    !(uncontended =
                      U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                    fullAddCount(x, uncontended);
                    return;
                }
                if (check <= 1)
                    return;
                s = sumCount();
            }
        // 尝试扩容
            if (check >= 0) {
                Node<K,V>[] tab, nt; int n, sc;
                while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                       (n = tab.length) < MAXIMUM_CAPACITY) {
                /* resizeStamp(n){ // RESIZE_STAMP_BITS = 16
                 *return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
                 *}
                 */
                    int rs = resizeStamp(n);
                    if (sc < 0) {
                    // RESIZE_STAMP_SHIFT = 16 ,下面的if语句判断条件是jdk的bug,在网上查资料一大佬提到 oracle bug库链接:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8214427
                    //下列的解释参考自:https://www.jianshu.com/p/749d1b8db066
                    // 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
                    // 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
                    // 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
                    // 如果 nextTable == null(结束扩容了)
                    // 如果 transferIndex <= 0 (转移状态变化了)
                    // 结束循环
                        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                            break;
                        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                            transfer(tab, nt); // 线程加入协同扩容
                    }
                    else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2))
                        transfer(tab, null); //扩容
                    s = sumCount();
                }
            }
        }
    View Code

    transfer()方法

      暂时没有认真研读,了解大概过程,这里先给参考链接,下次再仔细研读一下:

      https://www.jianshu.com/p/aaf769fdbd20

    总结

      相比 hashMap ,concurrentHashMap复杂很多,在处理并发安全的问题上,ConcurrenthahMap用到了 CAS + syschroynized,CAS这就要求了要了解java的内存模型,计算机的底层,所以在这些上面花了一部分时间,对 synchronized现在我也还是一知半解,要去啃啃源码,下次博客就要记录synchronized 和 lock、红黑树、线程池原理等等许多java知识,任重而道远。。。

    参考链接

    https://www.jianshu.com/p/c0642afe03e0

    https://www.jianshu.com/p/aaf769fdbd20

  • 相关阅读:
    Interview with BOA
    Java Main Differences between HashMap HashTable and ConcurrentHashMap
    Java Main Differences between Java and C++
    LeetCode 33. Search in Rotated Sorted Array
    LeetCode 154. Find Minimum in Rotated Sorted Array II
    LeetCode 153. Find Minimum in Rotated Sorted Array
    LeetCode 75. Sort Colors
    LeetCode 31. Next Permutation
    LeetCode 60. Permutation Sequence
    LeetCode 216. Combination Sum III
  • 原文地址:https://www.cnblogs.com/X-huang/p/10847507.html
Copyright © 2011-2022 走看看