Java数据结构笔试面试知识集合之ConcurrentHashMap

2019-07-04 00:00:00 集合 数据结构 笔试

Java笔试面试知识集合之总目录

之前关于HashMap的介绍见Java数据结构笔试面试知识集合之HashMap、HashTable,其中说到在并发环境中建议使用ConcurrentHashMap,所以总结一下ConcurrentHashMap

介绍

jdk 1.7 采用分段锁技术,整个 Hash 表被分成多个段,每个段中会对应一个 Segment 段锁,段与段之间可以并发访问,但是多线程想要操作同一个段是需要获取锁的。所有的 put,get,remove 等方法都是根据键的 hash 值对应到相应的段中,然后尝试获取锁进行访问。

可以理解为segment数组中的每一个segment中都包含一个和HashMap类似的数据结构。

《Java数据结构笔试面试知识集合之ConcurrentHashMap》
《Java数据结构笔试面试知识集合之ConcurrentHashMap》

jdk 1.8 取消了基于 Segment 的分段锁思想,改用 CAS + synchronized 控制并发操作,在某些方面提升了性能。并且追随 1.8 版本的 HashMap 底层实现,使用数组+链表+红黑树进行数据存储。

《Java数据结构笔试面试知识集合之ConcurrentHashMap》
《Java数据结构笔试面试知识集合之ConcurrentHashMap》

本篇主要介绍 1.8 版本的 ConcurrentHashMap ,有关其之前版本的实现情况,推荐阅读:

谈谈ConcurrentHashMap1.7和1.8的不同实现 ConcurrentHashMap在jdk1.8中的改进 ConcurrentHashMap原理分析(1.7与1.8)

官方注释

A hash table supporting full concurrency of retrievals and high expected concurrency for updates. This class obeys the same functional specification as Hashtable, and includes versions of methods corresponding to each method of Hashtable. However, even though all operations are thread-safe, retrieval operations do
not entail locking, and there is
not any support for locking the entire table in a way that prevents all access. This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.

源码

Constants(常量)

 /* 最大容量 */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /* 默认初始容量 - 必须是2的幂 */
    private static final int DEFAULT_CAPACITY = 16;

    /* 最大数组大小 与toArray()方法关联 */
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /* 默认并发级别 未使用 为了与此类的以前版本兼容 */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /* 默认加载因子 */
    private static final float LOAD_FACTOR = 0.75f;

    /* 红黑树相关 */
    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;
    private static final int MIN_TRANSFER_STRIDE = 16;

    /* resize相关 */
    private static int RESIZE_STAMP_BITS = 16;
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    static final int MOVED     = -1; // hash for forwarding nodes     static final int TREEBIN   = -2; // hash for roots of trees     static final int RESERVED  = -3; // hash for transient reservations     static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

Fields(域)

 /* table数组 初次插入时初始化 大小始终是2的幂 迭代器直接访问 */
   transient volatile Node<K,V>[] table;

   /* 扩容时用到的新table数组 非扩容时为null */
   private transient volatile Node<K,V>[] nextTable;

   /* 元素基础个数,通过CAS更新 */
   private transient volatile long baseCount;

   /**  * 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.  * 翻译:  * 控制Table初始化和扩容  * 当为负数时,表正在初始化或扩容,-1表示初始化,-(1+n)表示n个线程正在进行扩容  * 当Table为null时,保存要初始化Table的大小  * 默认为0  * 初始化后保存下一次扩容的阈值,相当于HashMap中的threshold  */
   private transient volatile int sizeCtl;

   /* 扩容时的下一个table索引 */
   private transient volatile int transferIndex;

   /* 在调整大小和/或创建CounterCells时使用的自旋锁(通过CAS锁定) */
   private transient volatile int cellsBusy;

   /* 用数组来处理当CAS失败时,元素统计提高效率的方案 */
   private transient volatile CounterCell[] counterCells;

   // views    private transient KeySetView<K,V> keySet;
   private transient ValuesView<K,V> values;
   private transient EntrySetView<K,V> entrySet;

Constructs(构造器)

  public ConcurrentHashMap() {
    }

    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;
    }

    public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, 1);
    }

Node(节点)

  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /* 用于map中的get()方法,在子类中被覆盖 */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

与HashMap中Node的区别

  1. val与next用volatile修饰,保证了可见性。
  2. 不支持setValue。
  3. find()方法用于map中的get()方法,在子类中被覆盖

Hash(索引)

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

★★put(添加)

  public V put(K key, V value) {
        return putVal(key, value, false);
    }

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        /* 不允许key和value为null */
        if (key == null || value == null) throw new NullPointerException();
        /* 计算key的hash值 */
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) { // 死循环             Node<K,V> f; int n, i, fh;
            /* 如果table为null,那么进行table初始化 */
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            /* 令f为hash值对应桶的首节点,如果f没有数据,那么直接添加节点 */
            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             }
            /* 如果f为forwardingNode,说明正在扩容,那么协助扩容 */
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            /* 如果f为普通节点,那么锁住f,在链表尾部或红黑树中添加节点 */
            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;
                            }
                        }
                    }
                }
                /* binCount!=0说明在链表或者红黑树中添加节点成功 */
                /* binCount==0说明将节点添加为某个桶的首节点 */
                if (binCount != 0) {
                    /* 链表长度超过8则转化为红黑树 */
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    /* oldVal!=null说明需要添加的节点已经存在,只是修改节点 */
                    /* 所以不需要进行扩容,直接返回oldVal */
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        /* 更新bigCount */
        addCount(1L, binCount);
        /* 添加节点而不是修改节点,返回null */
        return null;
    }

通过源码我们可以看出put方法分为以下几个步骤:

  1. 如果table没有初始化,就调用initTable()方法进行table初始化。
  2. 通过key的hash值计算索引i,如果table[i](桶的首节点)为null,那么直接添加节点。
  3. 如果正在进行扩容,那么协助进行扩容。
  4. 如果存在hash碰撞,就通过给桶的首节点加锁保证线程安全,在链表尾部或者红黑树中添加节点。如果链表长度超过8则转化为红黑树。
  5. 如果是修改节点直接返回,如果是添加节点元素个数+1,同时判断是否需要扩容。

注意

  • ConcurrentHashMap不支持key和value为null。
  • for (Node<K,V>[] tab = table;;)是一个死循环,除非节点添加成功或修改成功,否则不会跳出循环。
  • 当桶的首节点为null时没有通过加锁来添加节点,而是通过CAS机制来进行添加,如果操作成功则break跳出循环,如果添加失败会继续执行循环,通过后面在链表尾部或者红黑树中添加节点后跳出循环。

接下来具体分析一下putVal()中调用的一些关键方法或参数

initTable(初始化table)

   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             /* 说明还没有线程进行初始化,那么当前线程进行初始化,CAS更新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;
    }

initTable是如何保证只有一个线程进行初始化的?我们可以分为以下3种情况进行分析:

  1. 只有一个线程A执行initTable,A通过CAS更新sizeCtl的值成功,运行初始化操作代码,initTable方法结束。
  2. 有两个线程A和B,A通过CAS更新sizeCtl的值成功,运行初始化操作代码,操作还没有完成时B开始执行initTable,发现sizeCtl<0,所以B线程等待,A初始化操作完成后设置sizeCtl的值为为容量的0.75倍,initTable方法结束。此时table!=null,所以B退出while循环,initTable方法结束。
  3. 有两个线程A和B,A和B都发现sizeCtl不<0,所以A和B都想执行初始化代码,此时A和B都通过CAS更新sizeCtl的值,A更新成功,运行初始化操作代码,A初始化操作完成后设置sizeCtl的值为为容量的0.75倍,initTable方法结束。B更新失败,所以继续执行while循环,直到发现sizeCtl<0,所以B线程等待,等到table!=null,B退出while循环,initTable方法结束。(当然也可能B更新成功,A失败,那么结果反过来即可)

注意:sizeCtl是volatile变量,保证了其在多线程环境中的可见性。

ForwardingNode(扩容时插入桶的首部的节点)

   static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null, null);
            this.nextTable = tab;
        }

这个节点内部保存了一 nextTable 引用,它指向一张 hash 表。在扩容操作中,我们需要对每个桶中的结点进行分离和转移,如果某个桶结点中所有节点都已经迁移完成了(已经被转移到新表 nextTable 中了),那么会在原 table 表的该位置挂上一个 ForwardingNode 结点,说明此桶已经完成迁移。

ForwardingNode 继承自 Node 结点,并且它唯一的构造函数将构建一个键,值,next 都为 null 的结点,反正它就是个标识,无需那些属性。但是 hash 值却为 MOVED。

所以,我们在 putVal 方法中遍历整个 hash 表的桶结点,如果遇到 hash 值等于 MOVED,说明已经有线程正在扩容 rehash 操作,整体上还未完成,不过我们要插入的桶的位置已经完成了所有节点的迁移。

helpTransfer(协助扩容)

   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) {
            /* 返回一个 16 位长度的扩容校验标识 */
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                // sizeCtl 如果处于扩容状态的话                 // 前 16 位是数据校验标识,后 16 位是当前正在扩容的线程总数                 // 这里判断校验标识是否相等,如果校验符不等或者扩容操作已经完成了,                 // 直接退出循环,不用协助它们扩容了                 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                // 否则调用 transfer 帮助它们进行扩容                 // sc + 1 标识增加了一个线程进行扩容                 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

transfer(扩容)

   private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        /* 计算单个线程允许处理的最少table桶首节点个数,不能小于16 */
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range         /* 初始化nextTab */
        if (nextTab == null) {            // initiating             try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME                 sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        /* 定义 ForwardingNode 用于标记迁移完成的桶 */
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab         /* i指向当前桶,bound 指向当前线程需要处理的桶结点的区间下限 */
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            /* while循环通过--i遍历当前线程所分配到的桶结点,一个桶一个桶的处理 */
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                /* transferIndex <= 0 说明已经没有需要迁移的桶了 */
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                /* 更新 transferIndex为当前线程分配任务 */
                /* 处理的桶结点区间为(nextBound,nextIndex) */
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            /* 当前线程所有任务完成 */
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit                 }
            }
            /* 待迁移桶为空,那么CAS添加ForwardingNode结点标识该桶已经被处理过了 */
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            /* 如果扫描到 ForwardingNode,说明此桶已经被处理过了,跳过即可 */
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed             else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        /* 链表的迁移操作 */
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            /* for循环为了找到整个桶中最后连续的fh&n不变的结点 */
                            /* fh&n=0表明此节点更新后在table数组中的索引不变 */
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            /* 如果runBit=0,则nextTab[i]内ln前逆序,ln及其之后顺序 */
                            /* nextTab[i+n]内元素全部相对原table逆序,反之则相反 */
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            /* 把两条链表整体迁移到nextTab中 */
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            /* 将原桶标识位已经处理 */
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        /* 红黑树的迁移操作 */
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

扩容的步骤

  • 确定步长,多线程复制过程中防止出现混乱。每个线程分配步长长度的hash桶长度。最低不少于16。
  • 初始化nexttab。保证单线程执行,nexttab只存在于resize阶段,可以看作是临时表。
  • 构造Forword节点,以标志扩容完成的Hash桶。
  • 执行死循环
    • 分配线程处理hash桶的bound
    • 从n-1到bound,倒序遍历hash桶
    • 如果桶节点为空,CAS为Forword节点,表明处理完成
    • 如果桶节点为Forword,则跳过
    • 锁定桶节点,执行复制操作。在复制到nexttab的过程中,未破坏原tab的链表顺序和结构,所以不影响原tab的检索。
    • 复制完成,设置桶节点为Forword
    • 所有线程完成任务,则扩容结束,nexttab赋值给tab,nexttab置为空,sizeCtl置为原tab长度的1.5倍

addCount(更新baseCount)

   private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        /* CAS更新baseCount */
        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) {
                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();
            }
        }
    }

到此put方法介绍完毕

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;
    }

可能你会有所疑问,ConcurrentHashMap 中的 baseCount 属性不就是记录的所有键值对的总数吗?直接返回它不就行了吗?

之所以没有这么做,是因为我们的 addCount 方法用于 CAS 更新 baseCount,但很有可能在高并发的情况下,更新失败,那么这些节点虽然已经被添加到哈希表中了,但是数量却没有被统计。

还好,addCount 方法在更新 baseCount 失败的时候,会调用 fullAddCount 将这些失败的结点包装成一个 CounterCell 对象,保存在 CounterCell 数组中。那么整张表实际的 size 其实是 baseCount 加上 CounterCell 数组中元素的个数。

get

   public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

get 方法可以根据指定的键,返回对应的键值对,由于是读操作,所以不涉及到并发问题。

clear

   public void clear() {
        long delta = 0L; // negative number of deletions         int i = 0;
        Node<K,V>[] tab = table;
        while (tab != null && i < tab.length) {
            int fh;
            Node<K,V> f = tabAt(tab, i);
            if (f == null)
                ++i;
            else if ((fh = f.hash) == MOVED) {
                tab = helpTransfer(tab, f);
                i = 0; // restart             }
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> p = (fh >= 0 ? f :
                                       (f instanceof TreeBin) ?
                                       ((TreeBin<K,V>)f).first : null);
                        while (p != null) {
                            --delta;
                            p = p.next;
                        }
                        setTabAt(tab, i++, null);
                    }
                }
            }
        }
        if (delta != 0L)
            addCount(delta, -1);
    }

clear 方法将删除整张哈希表中所有的键值对,删除操作也是一个桶一个桶的进行删除。

参考资料

为并发而生的 ConcurrentHashMap(Java 8)

ConcurrentHashMap原理分析(1.7与1.8) – 明志健致远 – 博客园

java8集合框架(三)-Map的实现类(ConcurrentHashMap)

以上内容如有疏漏或错误敬请指出

    原文作者:燕燕于飞差池其羽
    原文地址: https://zhuanlan.zhihu.com/p/35853397
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章