源码分析——ConcurrentHashMap的spread,put,size方法原理分析

Posted 喵喵7781

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析——ConcurrentHashMap的spread,put,size方法原理分析相关的知识,希望对你有一定的参考价值。

 

 

ConcurrentHashMap的HashCode方法


    /**
     * Spreads (XORs) higher bits of hash to lower and also forces top
     * bit to 0. Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int spread(int h) 
        return (h ^ (h >>> 16)) & HASH_BITS;
    

跟HashMap的hash算法类似,只是把位数控制在int最大整数之内。

 

ConcurrentHashMap的put方法

 /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <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);
    

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) 

        //1.校验参数是否合法
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;

        //2.遍历Node
        for (Node<K,V>[] tab = table;;) 
            Node<K,V> f; int n, i, fh;
            
            //2.1.Node为空,初始化Node
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();

            //2.2.CAS对指定位置的节点进行原子操作
            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
            

            //2.3.如果Node的hash值等于-1,map进行扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);

            //2.4.如果Node有值,锁定该Node。
            //如果key的hash值大于0,key的hash值和key值都相等,则替换,否则new一个新的后继Node节点存放数据。
            //如果key的hash小于0,则考虑节点是否为TreeBin实例,替换节点还是额外添加节点。

            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;
                                
                            
                        
                        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);
                    if (oldVal != null)
                        return oldVal;
                    break;
                
            
        

        //3.计算map的size
        addCount(1L, binCount);
        return null;
    

什么是CAS操作?

compare and swap(比较并交换),在unsafe类里面有三个实现:compareAndSwapObject 、compareAndSwapInt、compareAndSwapLong。以compareAndSwapObject为例:如果内存值obj=期望值expect,则更新offset处的内存值为update。

/***
   * Compares the value of the object field at the specified offset
   * in the supplied object with the given expected value, and updates
   * it if they match.  The operation of this method should be atomic,
   * thus providing an uninterruptible way of updating an object field.
   *
   * @param obj the object containing the field to modify.
   * @param offset the offset of the object field within <code>obj</code>.
   * @param expect the expected value of the field.
   * @param update the new value of the field if it equals <code>expect</code>.
   * @return true if the field was changed.
   */
  public native boolean compareAndSwapObject(Object obj, long offset,
                                             Object expect, Object update);
  

 

其中用到Unsafe提供了三个原子操作,如下:

    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) 

        //获取obj对象中offset偏移地址对应的object型field的值,支持volatile load语义
        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) 

        //在obj的offset位置比较object field和期望的值,如果相同则更新。
        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) 

        //设置obj对象中offset偏移地址对应的object型field的值为指定值。
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    

初始化表如下:

    /**
     * Table initialization and resizing control.  When negative, the
     * table is being initialized or resized: -1 for initialization,
     * else -(1 + the number of active resizing threads).  Otherwise,
     * when table is null, holds the initial table size to use upon
     * creation, or 0 for default. After initialization, holds the
     * next element count value upon which to resize the table.
     */
   
    private transient volatile int sizeCtl;


    /**
     * 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) 

            //如果当前Node正在进行初始化或者resize(),则线程从运行状态变成可执行状态,cpu会从可运行状态的线程中选择
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin

            //如果当前Node没有初始化或者resize()操作,那么创建新Node节点,并给sizeCtl重新赋值
            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];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    
                 finally 
                    sizeCtl = sc;
                
                break;
            
        
        return tab;
    

sizeCtl这个参数用于表初始化和resize控制。当表正在初始化或resize的时候,-1表示初始化,或者-(1+resize的总线程数)。除此以外,当table为null时,初始表大小设置为创建时候的大小,或者0,或者默认值。初始化后,保持下一个元素计数值,用于调整表的大小。

 

ConcurrentHashMap的size()方法

HashMap的size()是在put的时候对size进行++size的操作,每增加一个元素size大小自增1,调用size()的时候,直接赋值即可获得。

ConcurrentHashMap的计算map中映射的个数,可以由mappingCount完全替代,因为ConcurrentHashMap可能包含的映射数多于返回值为为int的映射数。该返回的值是估计值,实际数量可能会因为并发插入或删除而有所不同。

ConcurrentHashMap的size方法是一个估计值,并不是准确值。

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

  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;
  

 /**
     * Returns the number of mappings. This method should be used
     * instead of @link #size because a ConcurrentHashMap may
     * contain more mappings than can be represented as an int. The
     * value returned is an estimate; the actual count may differ if
     * there are concurrent insertions or removals.
     *
     * @return the number of mappings
     * @since 1.8
     */
    public long mappingCount() 
        long n = sumCount();
        return (n < 0L) ? 0L : n; // ignore transient negative values
    

 

参考资料:

JDK1.8的ConcurrentHashMap源码

Java 8 ConcurrentHashMap源码分析

ConcurrentHashMap源码分析(1.8) 

ConcurrentHashMap源码分析(JDK8版本)

Java中Unsafe类详解

 

 

 

 

 

以上是关于源码分析——ConcurrentHashMap的spread,put,size方法原理分析的主要内容,如果未能解决你的问题,请参考以下文章

ConcurrentHashMap源码分析(1.8)

JDK源码ConcurrentHashMap源码分析

ConcurrentHashMap源码解析文章总目录

ConcurrentHashMap源码分析

JUCJDK1.8源码分析之ConcurrentHashMap

高阶源码分析:ConcurrentHashMap