ConcurrentHashMap

参考资料
小刘老师将源码

重要参数

 // 散列表最大值
 private static final int MAXIMUM_CAPACITY = 1 << 30;
 // 负载因子,负责计算扩容阈值
 private static final float LOAD_FACTOR = 0.75f;
 // 树化阈值
 static final int TREEIFY_THRESHOLD = 8;
 // 非树化的阈值
 static final int UNTREEIFY_THRESHOLD = 6;
 // 最小树化容量,散列表的容量达到 64 且链表长度达到 8 的时候才可以进行树化
 static final int MIN_TREEIFY_CAPACITY = 64;
 // 每个线程迁移数据时,桶位的最小步长
 private static final int MIN_TRANSFER_STRIDE = 16;
 // 扩容的标识戳
 private static final int RESIZE_STAMP_BITS = 16;
 // 最大线程数量 65535
 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
 // 正在迁移数据 ForwardingNode 用到
 static final int MOVED     = -1;
 // 树化结构
 static final int TREEBIN   = -2;
 // 0x7fffffff 相当于 31个1 在与负数进行按位与运算时会得到一个正数
 static final int HASH_BITS = 0x7fffffff
 // 装载数据的桶
 transient volatile Node<K,V>[] table;
 // 扩容过程中使用的数组,扩容结束后指向 NULL,会赋值给 ForwardingNode
 private transient volatile Node<K,V>[] nextTable;
 // 当数组未发生竞争或者数组中要修改的位置被其他线程正在修改时,将要修改的值记录到 baseCount 中
 private transient volatile long baseCount;
 // 表示当前是否有其他线程正在操作数组,通过循环和 CAS 来修改 cellsBusy 的值来表示锁,1 代表被上锁,0 代表无锁状态
 private transient volatile int cellsBusy;
 // LongAdder 中的 cells 数组,当 BaseCount 发生竞争后,会创建 Cells 数组。线程会通过 hash 值取到自己的 cell. 将增量累加到指定的 cell 中。总数
 private transient volatile CounterCell[] counterCells;
 // 表示 table 的状态
 // -1 表示当前的 table 正在初始化
 // sizeCtl!=-1 && sizeCtl<0 时 表示正在扩容,高 16 位表示扩容的标识戳,低 16 位表示有多少个线程正在参与扩容
 // 0 表示创建 table 时使用 default capacity
 // 大于 0 时:
 // 如果 table 未初始化化,表示要初始化的大小
 // 如果 table 已经初始化,表示下一次要扩容的阈值
 private transient volatile int sizeCtl
 // 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;}
        
 // 重定向节点,扩容时使用,指向扩容后的 table
 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;
        }
        
 // native 层对应的地址的偏移 因为 CAS 是一个 Native 的操作,Java 层要知道取那段内存表示 native 层对应的值
  private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long SIZECTL;
    private static final long TRANSFERINDEX;
    private static final long BASECOUNT;
    private static final long CELLSBUSY;
    private static final long CELLVALUE;
    private static final int ABASE;
    private static final int ASHIFT;

    static {
        try {
            SIZECTL = U.objectFieldOffset
                (ConcurrentHashMap.class.getDeclaredField("sizeCtl"));
            TRANSFERINDEX = U.objectFieldOffset
                (ConcurrentHashMap.class.getDeclaredField("transferIndex"));
            BASECOUNT = U.objectFieldOffset
                (ConcurrentHashMap.class.getDeclaredField("baseCount"));
            CELLSBUSY = U.objectFieldOffset
                (ConcurrentHashMap.class.getDeclaredField("cellsBusy"));

            CELLVALUE = U.objectFieldOffset
                (CounterCell.class.getDeclaredField("value"));

            ABASE = U.arrayBaseOffset(Node[].class);
            int scale = U.arrayIndexScale(Node[].class);
            if ((scale & (scale - 1)) != 0)
                throw new Error("array index scale not a power of two");
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        }.....
    }

Spread 函数再 hash

使用扰动函数对 hash 值进行重新的计算,让高 16 位参通过异或算法(不同返回1)参与到 hash 的计算中,让 hash 值更散列。不经过扰动函数处理 hash 值,在数组比较小的时候,会只有低 16 位参与到计算,散列程度不够,容易发生碰撞

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

计算扩容标识

只有当扩容标识一致时,其他线程才可以参与扩容

static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

tableSizeOf

返回传入数字的最小的 2 的平方数

  1. 传入 7 返回 8
  2. 传入 16 返回 16
 private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

put 操作

put 函数是通过自旋和 CAS 方式实现的。

  1. 首先判断数组容器是否初始化了,如果没有进行初始化
  2. 如果当前插入位置没有元素,在当前位置插入元素
  3. 如果当前位置是 FWD 节点参与扩容
  4. 如果当前位置有元素,如果是链表且没有重复插入在尾部,如果重复了进行替换
  5. 如果插入位置是树,如果重复了同样进行替换
  6. 插入后,如果插入的位置达到了树化阈值,进行扩容
  7. 插入后,如果达到了扩容阈值,进行扩容
final V putVal(K key, V value, boolean onlyIfAbsent) {
        // ConcurrentHashMap 插入的 key 和 value 不能为空
        if (key == null || value == null) throw new NullPointerException();
        // 对 key 的 hash 值进行扰动,让 hash 值更散列
        int hash = spread(key.hashCode());
        // 插入的节点在 链表 中的位置
        int binCount = 0;
        // tab 持有了 table,也就是 ConcurrentHashMap 数组的引用
        for (Node<K,V>[] tab = table;;) {
            // f 表示头结点
            // n 表示数组的长度
            // i 表示 index,要插入的位置
            // fh 表示节点处理后的 hash 值
            Node<K,V> f; int n, i, fh;
            // Case 1: 第一次插入,先初始化数组,懒加载的方式
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // Case 2: 如果插入位置的头结点是 null,也就是当前位置还没有元素,直接插入
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 如果在这个位置通过 CAS 插入成功了,终止循环,
                // 如果返回 false 代表有其他线程先一步在这里插入了,继续自旋,尝试重新插入
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // Case 3:如果当前是 Forward 节点,代表正在扩容,协助扩容,Forward 节点的 hash 值固定是 MOVED (-2)
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            // Case 4:如果是树或者链表
            else {
                // 记录旧节点,在节点重复时使用
                V oldVal = null;
                // 锁住修改桶位的头结点。
                synchronized (f) {
                    // 确保没有其他线程修改了头结点,导致锁加错
                    if (tabAt(tab, i) == f) {
                        // hash 值大于 0 表示该节点没有在扩容
                        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;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    // 如果修改位置的元素长度达到了 树化阈值(一定是链表),进行树化
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        // 插入元素后,判断桶的长度是否达到了扩容阈值
        addCount(1L, binCount);
        return null;
    }

初始化数组

 private final Node<K,V>[] initTable() {
        // tab 表示 ConcurrentHashMap 的数组
        // sc 表示 sizeCtl
        // -1 表示正在被其他线程初始化
        // <0 表示正在被扩容
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            // 当前线程没有争抢都初始化锁,继续自旋
            if ((sc = sizeCtl) < 0)
                // 让出一下 cpu 
                Thread.yield(); // lost initialization race; just spin
            // 尝试争抢锁 通过 CAS 的方式将 sizeCtl 修改成为 -1,
            // 修改成功代表获取锁成功,修改失败继续自旋
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        // 如果 sc > 0 使用 sc 的值进行初始化
                        // 如果 sc == 0 使用 默认容量进行初始化
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        // 计算下一次扩容的值
                        // 向右移动 4 位,代表除以 4
                        // n - (1/4n) = 3/4n = 0.75n
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

统计散列表中元素个数以及数组扩容

addCount 函数上半段表示统计散列表中元素个数 通过 CellCounter 来实现。下半段为扩容函数

  1. 采用了CAS+BaseCount+自旋锁的方式
  2. 当没有发生争抢时,就将当前线程记录的数值累加到 baseCount 上
  3. 如果发生了争抢,就将值写到当前线程在 Cells 数组对应的位置上
  4. 在计算元素总和时,将 Cells 数组中的值和 BaseCount 进行累加
  5. 通过 CAS 修改 sizeCtl 的结果,判断是否满足扩容条件。
  6. 如果是第一个扩容线程,需要准备一个新的数组
  7. 如果是参与扩容的线程,需要传递扩容后的数组来参与扩容
 // 计算散列表中元素的个数,如果达到扩容阈值,进行扩容
 private final void addCount(long x, int check) {
        // as 当前线程经经过寻址计算后在数组对应位置存储值
        // b baseCount 在没有发生竞争时,值保存在 baseCount 中
        // s 表示当前散列表中的元素
        CounterCell[] as; long b, s;
        // 条件1 counterCells 已经创建好了
        // 条件2 U.compareAndSwapLong 更新 base 失败了,表示有其他线程修改过了,发生了竞争,取反后表示 false 进入循环
        // 注意 这里是或关系,满足一个条件就进入循环
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            // 当前线程在 CounterCell 中对应的元素
            // v 更新的值
            // m Cells 数组的元素个数
            CounterCell a; long v; int m;
            // 是否没有竞争
            boolean uncontended = true;
            // 条件1 as == null || (m = as.length - 1) < 0
            //   true 表示当前线程是通过写 base 竞争失败进入的循环
            // 条件2 a = as[ThreadLocalRandom.getProbe() & m]) == null
            //   true 表示当前线程所对应的位置没有存储值
            // 条件3  !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
            //   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);
                // 因为 fullAddCount 是很耗时的方法,后面是扩容方法,所以在这里就直接返回,不参与扩容操作了
                return;
            }
            if (check <= 1)
                return;
            // 获取当前散列表中元素的个数,是一个期望值。是一个最终一致性的。
            s = sumCount();
        }
        // 表示是 put 操作调用的 addCount 操作
        if (check >= 0) {
           // 进行扩容 
           // tab: map 的散列表数组
           // n: 散列表元素的个数
           // sc:sizeCtl: 表示当前是否正在扩容
            Node<K,V>[] tab, nt; int n, sc;
            // True 表示可以扩容
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                // 计算一个扩容标志,当其他线程加入时使用
                int rs = resizeStamp(n);
                // sizeCtl 小于0  表示当前正在扩容,判断是否可以加入扩容过程中
                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))
                        // 协助扩容 带有 nexttable
                        transfer(tab, nt);
                }
                // 第一个扩容的线程
                // sizeCtl 大于 0 表示达到了扩容阈值,可以扩容
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // 第一个扩容的线程
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

数据迁移 Transfer

  1. 扩容时会计算扩容的步长,就是每个线程负责桶位哪个区间的扩容
  2. 然后通过自旋加 CAS 的方式更新节点
  3. 如果当前节点没有元素,就在原表中该位置处插入一个 FWD 的节点
  4. 如果当前节点是一个链表,会将该链表分为高位链表和低位链表(高位链表和新数组长度相与后是 1 ,低位相与后是 0 ),然后通过 CAS 的方式将低位链表放到新数组中,位置不变
  5. 高位链表放到原位置加上原数组长度的位置处,并将原数组对应位置更新为 FWD 节点
  6. 如果是树,同样区分为高低位节点,移动到新数组中,并且检查是否可以退化为链表
 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        // n 原数组的长度
        // stride:步长,表示每个数据负责迁移一段数据区间的长度
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        // 如果是第一个扩容的线程,创建一个新表
        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;
        // 重定向节点,记录新表的位置,当有其他线程访问正在扩容的节点时,会直接通过 FWD 去访问新表
        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;;) {
            // f 当前桶位的头结点
            // fh hash 值
            Node<K,V> f; int fh;
            // 1 给当前线程分配任务区间
            // 2 维护任务的进度
            while (advance) {
                // nextIndex 任务开始下标
                // nextBound 任务结束下标
                int nextIndex, nextBound;
                // 表示当前线程还有任务没有处理完,
                // --i>= bound  就是当前要处理的下一个任务还没有达到边界
                if (--i >= bound || finishing)
                    advance = false;
                // 表示还没有分配任务区间
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                // 根据步长分配任务区间
                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;
                }
                // sc - 1 表示扩容让 sizeCtl 的记录扩容线程数 -1 
                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
                }
            }
            
            
            // 进行扩容
            // 条件1:当前节点没有要迁移的元素,直接在该位置添加 FWD 节点
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            // 如果当前节点已经在迁移中,继续迁移
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                // 给头结点加锁
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        // ln 表示低位节点
                        // hn 表示高位节点
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            // 使用 runBit 来区分高位节点和低位节点
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            // 低位节点相与后是 0 
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            // 高位节点相与后是 1
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            // 构建 ln 和 hn
                            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);
                            }
                            // 将低位链表放回原来的位置
                            setTabAt(nextTab, i, ln);
                            // 高位链表放到原来位置加上原来表长度的位置上
                            setTabAt(nextTab, i + n, hn);
                            // 将旧表的位置改为 FWD 节点。
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            // lo 低位链头节点  loTail 低位链尾结点
                            TreeNode<K,V> lo = null, loTail = null;
                            // hi 高位链头结点  hiTail 高位链尾节点
                            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);
                            // 将该节点在原表处设置为 Forward 节点
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

参与扩容 helpTransfer

  1. 参与扩容时首先会判断当前是否已经扩容完成了
  2. 当前参与的线程没有达到最大并发数
  3. 当前要迁移的元素没有被分配完
   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;
                // 如果更新成功,就将扩容线程数+1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {   
                    // 将新表传递进去参与扩容
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

获取元素 get

   public V get(Object key) {
        // e 头结点
        // p 目标节点
        // n 是当前数组长度
        // eh e的hash 值
        // ek 当前节点的 key
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        // 如果 table 已经初始化了,key 所对应的位置有元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            // 如果头节点的 hash 值和内容符合要求,返回头结点对应的 value
            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;
    }

ForwardingNode 的 find 方法

Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: 
            // e = tabAt(tab, (n - 1) & h))  在新表中重新进行寻址,找到对应位置的头节点
            for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;
                for (;;) {
                    int eh; K ek;
                    // 如果新表命中直接返回对应的元素
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    if (eh < 0) {
                        // 如果是一个 forward 节点,继续去新表中查询
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        // 去 TreeBin 中查询
                        else
                            return e.find(h, k);
                    }
                    // 如果是链表继续遍历
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }

删除节点

删除节点实际上是节点的替换

public V remove(Object key) {
        return replaceNode(key, null, null);
    }
  final V replaceNode(Object key, V value, Object cv) {
        // 进行寻址
        int hash = spread(key.hashCode());
        for (Node<K,V>[] tab = table;;) {
            // f 头结点 n 数组长度 i 目标元素下表 fh 头结点的hash值
            Node<K,V> f; int n, i, fh;
            // case1 tab 尚未初始化
            // case2 当前位置没有元素
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            // 如果当前正在扩容,需要参与到扩容过程中
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                
                V oldVal = null;
                // 加锁是否成功的标识
                boolean validated = false;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            // 加锁成功
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                // 如果头节点命中
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    // 如果 cv 传入的是 null 就是一个删除操作
                                    // 如果 cv == ev 就是一个替换操作
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        // 记录移除的值
                                        oldVal = ev;
                                        // 如果传入 value 不是 null 进行替换
                                        if (value != null)
                                            e.val = value;
                                        // 删除操作
                                        else if (pred != null)
                                            // 非头结点 删除节点前一个指向删除节点后一个节点
                                            pred.next = e.next;
                                        else
                                            // 头结点
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                pred = e;
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        else if (f instanceof TreeBin) {
                            validated = true;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    oldVal = pv;
                                    if (value != null)
                                        p.val = value;
                                    else if (t.removeTreeNode(p))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                // 删除成功计数减1
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 1.ConcurrentHashmap简介 在使用HashMap时在多线程情况下扩容会出现CPU接近100%的情况...
    你听___阅读 4,618评论 2 20
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • 一段恋情,跟时间长短没有过多的联系! 一份记忆,跟结局好坏没有过多的关系! 恋爱了许久,最终连背影都没能留下。 分...
    方城子夜阅读 300评论 0 1
  • ——人生在世,究竟是命由天定抑或逆天改命?!对于这一命题想必是众说纷纭,各执一词。机缘巧合,经鲁哥推荐,我通览整本...
    吴蜀魏阅读 281评论 0 0
  • 2011年,那年我上大一,在一个不遥远,不繁华的城市。在城市淫浸一年的我,回到家乡的小县城时心里有一种巨大的落差感...
    熊猫微刊阅读 379评论 0 3