对比看ConcurrentHashMap之二

主要方法

tabeSizeFor

两个类中的tableSizeFor实现基本上一致,不细说。

hash

HashMap中的hash方法:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

ConcurrentHashMap中的计算hash的方法名叫做spread

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS; // static final int HASH_BITS = 0x7fffffff; 
    }

这里的h传入的也是key.hashCode()。可以看到基本实现是类似的,不过又做了一次按位与。这个按位与,除了保证首位是0,也就是正数,别的没啥作用。

每个十六进制数4bit,因此8位16进制是4个字节,刚好是一个int整型

F的二进制码为 1111

7的二进制码为 0111

这样一来,整个整数 0x7FFFFFFF 的二进制表示就是除了首位是 0,其余都是1,就是说,这是最大的整型数 int(因为第一位是符号位,0 表示他是正数)

用 INT_MAX 常量可以替代这个值。

tabAt & casTabAt & setTabAt
    /* ---------------- Table element access -------------- */

    /*
     * Volatile access methods are used for table elements as well as
     * elements of in-progress next table while resizing.  All uses of
     * the tab arguments must be null checked by callers.  All callers
     * also paranoically precheck that tab's length is not zero (or an
     * equivalent check), thus ensuring that any index argument taking
     * the form of a hash value anded with (length - 1) is a valid
     * index.  Note that, to be correct wrt arbitrary concurrency
     * errors by users, these checks must operate on local variables,
     * which accounts for some odd-looking inline assignments below.
     * Note that calls to setTabAt always occur within locked regions,
     * and so in principle require only release ordering, not
     * full volatile semantics, but are currently coded as volatile
     * writes to be conservative.
     */

    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }
tabAt

此函数返回table数组中下标为i的结点,可以看到是通过Unsafe对象通过反射获取的,getObjectVolatile的第二项参数为下标为i的偏移地址。

但是这边为什么i要等于((long)i << ASHIFT) + ABASE呢,

计算偏移量 ASHIFT是指tab[i]中第i个元素在相对于数组第一个元素的偏移量,而ABASE就算第一数组的内存素的偏移地址

所以呢,((long)i << ASHIFT) + ABASE就是i最后的地址

casTabAt

此函数用于比较table数组下标为i的结点是否为c,若为c,则用v交换操作。否则,不进行交换操作

setTabAt

把节点v放入tab的i下标位置。注意上面的注释:

Note that calls to setTabAt always occur within locked regions, and so in principle(大体上) require only release ordering, not full volatile semantics(语义), but are currently coded as volatile writes to be conservative(保守的).

size

ConcurrentHashMap的size跟HashMap的不同,由于存在扩容+并发的情况,这里的实现会有所不同。先看源码:

    public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }

关键在sumCount上,继续翻:

 /* ---------------- Counter support -------------- */

    /**
     * A padded cell for distributing counts.  Adapted from LongAdder
     * and Striped64.  See their internal docs for explanation.
     */
    @sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

    final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

首先 counterCells数组就是volatile修饰的,Countercell中的value也是volatile类型的,这里最终是需要把value值累加得到ConcurrentHashMap的长度。

其中baseCount是正常情况下ConcurrentHashMap中的元素个数,而CounterCells数组则是在高并发场景下,cas失败的数据在重试过程中的元素个数。ConcurrentHashMap的size是二者之和。详看put中的addCount方法。

不过

get

先上源码,并且在源码中增加注释

    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code key.equals(k)},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @throws NullPointerException if the specified key is null
     */
    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        
        int h = spread(key.hashCode());  // 1 调用spread方法获取key的hash值
        
        // 2 根据tabAt方法确定key.hash 对应index的节点不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            // 2.1 直接命中,返回结果
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 2.2 eh = e.hash,且小于0,那么说明节点在树上,通过e.find 来查找对应节点
            // 需要注意的是,eh的值是-1 -2,对应的分别是ForwardingNode和TreeBin,实现还是不同的。
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            
            // 2.3 如果eh > 0 并且当前链表有其它元素,通过while循环遍历链表,并返回
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

可以看到,主要实现还比较简单,需要注意的是,如果节点在树上,这里调用了Node节点的find方法来实现,不过根据上图可以知道,Node节点有好几个子类,每个子类都各自实现了find方法(TreeNode\TreeBin\ForwardingNode)。

put

先看源码:

    /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     * 
     * key、value都不能为空,这点跟hashmap不一样。
     * 
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */
    public V put(K key, V value) {
        return putVal(key, value, false);  // 主要实现还在putVal中
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 1 如果任一为空,直接NPE
        if (key == null || value == null) throw new NullPointerException();
         
        // 2 获取key的hash值
        int hash = spread(key.hashCode());
        
        int binCount = 0;
        // 3 这边加了一个循环,就是不断的尝试,因为在table的初始化和casTabAt用到了compareAndSwapInt、compareAndSwapObject,因为如果其他线程正在修改tab,那么尝试就会失败,所以这边要加一个for循环,不断的尝试
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 3.1 如果table中没有数据,需要做table的初始化            
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();  
            
            // 3.2 如果对应位置上没有数据,创建一个节点,用casTabAt方法,将Node节点替换掉null,也就是直接设值
            // 注意,这里还做了一个赋值,f=tabAt,也就是根据hash获取所在对应节点
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 3.3 如果当前节点f不为null,且f的hash值是-1,则f是forWardingNode类型节点,表示正在扩容resize
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f); // 3.3.1 帮助resize
            // 3.4 值不为-1,需要判断是链表还是树
            else {
                V oldVal = null;
                //这个地方设计非常的巧妙,内置锁synchronized锁住了f,因为f是指定特定的tab[i]的头结点,
                // 所以就锁住了整行链表,这个设计跟分段锁有异曲同工之妙,只是其他读取操作需要用cas来保证
                synchronized (f) { 
                    // 3.4.1 新节点放在链表中,存在key重合则覆盖,否则新增
                    if (tabAt(tab, i) == f) { // i = (n - 1) & hash, 也就是indexFor
                        if (fh >= 0) { // f节点的值大于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;
                                }
                            }
                        }
                        // 3.4.2 头节点是树的根节点,则新增节点放到树上
                        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;
                            }
                        }
                    }
                }
                // 3.5 如果超过树化阈值,执行转换成红黑树的操作
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

用到的initTable

    /**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0) //如果sizeCtl小于0,说明有其他线程在初始化,交出cpu资源
                Thread.yield(); // lost initialization race; just spin 
            
            // 否则sc = sizeCtl = 0,刚开始sizeCtl取默认值,是0.
            // 这里要将SIZECTL设置为-1(前提是sc的值是0),详见下面的compareAndSwapInt源码
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -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];//默认创建DEFAULT_CAPACITY长度
                        table = tab = nt; // 赋值
                        sc = n - (n >>> 2); // n >>> 2 带符号右移2位,相当于n/4,所以sc = 0.75*n
                    }
                } finally {
                    sizeCtl = sc; // 把sizeCtl设置为0.75*容量
                }
                break;
            }
        }
        return tab;
    }
    /**
     * Atomically update Java variable to <tt>x</tt> if it is currently
     * holding <tt>expected</tt>.
     * @return <tt>true</tt> if successful
     */
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

用到的扩容操作,这里的扩容不根据容量,而是根据sizeCtl,这点儿跟HashMap也不同。

    /**
     * Helps transfer if a resize is in progress.
     */
    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

用到的addCount操作

   /**
     * Adds to count, and if table is too small and not already
     * resizing, initiates transfer. If already resizing, helps
     * perform transfer if work is available.  Rechecks occupancy
     * after a transfer to see if another resize is already needed
     * because resizings are lagging additions.
     *
     * @param x the count to add
     * @param check if <0, don't check resize, if <= 1 only check if uncontended(竞争)
     */
    private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        
        // 这里会尽量去增加baseCount的值,如果失败,才放入CounterCells
        // counterCells不为空,说明其他线程有cas更新baseCount失败的数据
        if ((as = counterCells) != null ||   
            // 或者set baseCount的值失败
            !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))) {  //CounterCell中的value
                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) {
                int rs = resizeStamp(n);
                if (sc < 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();
            }
        }
    }


    ......
       
    // See LongAdder version for explanation
    private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;
            if ((as = counterCells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {            // Try to attach new Cell
                        CounterCell r = new CounterCell(x); // Optimistic create
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            try {               // Recheck under lock
                                CounterCell[] rs; int m, j;
                                if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                    try {
                        if (counterCells == as) {// Expand table unless stale
                            CounterCell[] rs = new CounterCell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            counterCells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = ThreadLocalRandom.advanceProbe(h);
            }
            else if (cellsBusy == 0 && counterCells == as &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {                           // Initialize table
                    if (counterCells == as) {
                        CounterCell[] rs = new CounterCell[2];
                        rs[h & 1] = new CounterCell(x);
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          // Fall back on using base
        }
    }

这块代码写的还是挺绕,有空再仔细抠一下。不过主要理解思想就好。

参考资料:

https://www.cnblogs.com/leesf456/p/5453341.html

https://cloud.tencent.com/developer/article/1102181

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

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

http://www.cnblogs.com/huaizuo/archive/2016/04/20/5413069.html

猜你喜欢

转载自blog.csdn.net/weixin_34146410/article/details/87420618