concurrenthashmap 源码分析(一) - putVal() 方法

concurrenthashmap 系列的源码分析 主要是 介绍了putVal方法的源码,这里面也包括了 concurrenthashmap 大部分的常用的方法,例如 initTable、 spread、transfer、treeifyBin、addCount等等

带着问题开始吧!答案会在concurrenthashmap 系类文章的某个角落。。。concurrenthashmap 源码分析(一) - putVal() 方法_第1张图片

question*******1   hashmap扩容时每个entry需要再计算一次hash吗 ?:不需要

question*******2 hashmap的数组长度为什么要保证是2的幂?

一、概述 

A hash table supporting full concurrency of retrievals and high expected concurrency for updates
支持检索的完全并发性和更新的高期望并发性的哈希表

二、重要属性

1. sizeCtl

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

用来控制初始化和扩容的属性。 -N: 表示正有N-1个线程执行扩容操作

concurrenthashmap 源码分析(一) - putVal() 方法_第2张图片

2. transferIndex

/**
     * The next table index (plus one) to split while resizing.
     * 扩容索引,表示已经分配给扩容线程的table数组索引位置。主要用来协调多个线程,并发安全地
     * 获取迁移任务(hash桶)
     */
    private transient volatile int transferIndex;

3.用来计算concurrentHashMap长度的属性 (可以参考longAddr) 

  /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    /**
     * baseCount用于记录元素的个数,对这个变量的修改操作是基于CAS的.
     */
   /**
     * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
     */
    private transient volatile int cellsBusy;

    /**
     * counterCells是一个元素为CounterCell的数组,该数组的大小与当前机器的CPU数量有关,
     * 并且它不会被主动初始化,只有在调用fullAddCount()函数时才会进行初始化。
     */
    /**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;

 

三、putVal方法

/** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        //计算hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        //死循环 何时插入成功 何时跳出
         (1)数组要插入的位置没有值 (2)链表或树中插入成功
        for (Node[] tab = table;;) {
            Node f; int n, i, fh;
            //如果table为空的话,初始化table
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //根据hash值计算出在table里面的位置 question*******2 
长度16或其他2的幂次方,Length - 1的值的二进制所有的位均为1,这种情况下,Index的结果等于hashCode的最后几位。只要输入的hashCode本身符合均匀分布,Hash算法的结果就是均匀的。
一句话,HashMap的长度为2的幂次方的原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀分布。
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            	//如果这个位置没有值 ,直接放进去,不需要加锁
                if (casTabAt(tab, i, null,
                             new Node(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //帮助扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //锁住桶中的头结点,保证之后插入的并发安全  
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        //fh〉0 说明这个节点是一个链表的节点 不是树的节点  spread方法保证了结果非负数
                        if (fh >= 0) {
                            binCount = 1;
                            //在这里遍历链表所有的结点
                            for (Node e = f;; ++binCount) {
                                K ek;
                                //如果hash值和key值相同 onlyIfAbsent=false 则覆盖对应结点的value值
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node pred = e;
                                //如果遍历到了最后一个结点,那么就证明新的节点需要插入 就把它插入在链表尾部
                                if ((e = e.next) == null) {
                                    pred.next = new Node(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //如果这个节点是树节点,就按照树的方式插入值(还没太研究明白。。。)
                        else if (f instanceof TreeBin) {
                            Node p;
                            binCount = 2;
                            if ((p = ((TreeBin)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                	//如果链表长度已经达到临界值8 (treeifyBin 中会再次判断数组长度>64) 就需要把链表转换为树结构
                    /**
                    *某个桶的元素大小超过了8个,达到了转换为红黑树的阈值,但是数组的长度<64,此 
                    *时只是通过扩容来解决容量问题,而不会转换为红黑树,而过数组长度>=64,则可以 
                    *转换为红黑树;
                    */
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //将当前ConcurrentHashMap的元素数量+1
        addCount(1L, binCount);
        return null;
    }

 

下面附上流程图:

concurrenthashmap 源码分析(一) - putVal() 方法_第3张图片

 

ps:其他方法待续。。。。

你可能感兴趣的:(多线程,数据结构)