ConcurrentHashMap 1.7 & 1.8

Gemini Enterprise免费试用攻略:解锁企业级AI平台的隐藏技巧与避坑指南 本文详细介绍了Gemini Enterprise免费试用的全流程攻略,包括注册优化、团队协作、资源管理和典型应用场景。通过解锁企业级AI平台的核心功能如Gemini 3.0 Pro模型和Nano Banana图像生成系统,帮助企业高效评估AI与业务的契合度。文章还提供了安全合规与数据管理的实用建议,确保试用过程顺畅无忧。 阅读详情

目录

1.7

特点 

初始化

1. 重要属性 

2. 构造方法

3.Segment 内重要属性

4.HashEntry 内重要属性

get()

put()

1. Segment -> put()

2. Segment -> scanAndLockForPut()

rehash()

size() 

1.8

特点 

重要属性

addCount()

tryPresize()

helpTransfer() 

transfer 


 

1.7

特点 

图片来源:ConcurrentHashMap中有十个提升性能的细节,你都知道吗?ConcurrentHashMap的源码常读常新!https://mp.weixin.qq.com/s?__biz=MzkyMjIzOTQ3NA==&mid=2247484633&idx=1&sn=87cb3d1f0670f598e4c6b72f0e188132&source=41#wechat_redirect

  1. 使用分段锁 Segment,继承自  ReentrantLock;只对某一段进行加锁,不会进行全局加锁;锁之间互不影响,提高并行度。
  2. Segment 内部为 HashEntry 数组,等同于 HashMap 内部结构。
  3. 发生哈希冲突,连接成链表,采用头插法。

初始化

1. 重要属性 

// 默认 map 大小,即 HashEntry 的默认数量
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 默认并行度,即 segment 数组大小(分段锁数量)
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 每个分段最小容量,即最少 HashEntry 数量
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
// 每个分段最大容量
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
// 默认自旋次数,当自旋次数超过此值,则阻塞等待锁释放;
// 避免执行 size() 时,map 被频繁更新,导致无限进行自旋,影响性能
static final int RETRIES_BEFORE_LOCK = 2;
// 用于索引 segment 的掩码值,key 哈希值的高位用于选择 segment
final int segmentMask;
// 用于索引 segment 时,使用的偏移值,以此得到 key 哈希值的高位
final int segmentShift;
// Segment 数组 
final Segment<K,V>[] segments;

2. 构造方法

// initialCapacity HashEntry 的数量;
// loadFactor 负载因子;
// concurrencyLevel 并行度(segment 数量,分段锁数量)
public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    // 幂次
    int sshift = 0;
    // 并行度(segment 数量,分段锁数量),为 2 ^ sshift 
    int ssize = 1;
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    // 索引 segment 时,使用的偏移值;
    // 右移 segmentShift 位,得到 key 哈希值高 sshift 位
    this.segmentShift = 32 - sshift;
    // 索引 segment时,使用的掩码
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    // 每个 segment 里,HashEntry 的数量;
    // 等于,大于 initialCapacity / ssize 的,2 的幂次
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    // 创建第一个 segment;其他均为懒加载,并以 segments[0] 为模板
    Segment<K,V> s0 =
        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                         (HashEntry<K,V>[])new HashEntry[cap]);
    // 初始化 segments
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    // 保证可见性
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
}

3.Segment 内重要属性

// scanAndLockForPut中自旋循环获取锁的最大自旋次数
static final int MAX_SCAN_RETRIES =
    Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
// 键值对,存储结构
transient volatile HashEntry<K,V>[] table;
// 实际键值对个数;无 volatile 修饰,不保证可见性
transient int count;
// segment元素修改次数记录
transient int modCount;
// 扩容阈值
transient int threshold;
// 负载因子
final float loadFactor;

4.HashEntry 内重要属性

final int hash;
final K key;
// 确保可见性
volatile V value;
// 确保可见性
volatile HashEntry<K,V> next;

get()

// 没有加锁操作,弱一致性换取性能
public V get(Object key) {
    Segment<K,V> s; // manually integrate access methods to reduce overhead
    HashEntry<K,V>[] tab;
    // 获取 key 对应的 hash 值
    int h = hash(key);
    // 通过 hash 值高位计算得到 Segment 的偏移量
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    // 使用 volatile 读,获取对应的 Segment
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        // HashEntry[] 不为空
        (tab = s.table) != null) {
        // 1. 通过 hash 值低位计算得到 HashEntry 的偏移量;
        // 2. 使用 volatile 读,获取 HashEntry 的链表头结点;进行遍历
        // 3. happens-before 原则:①volatile 写在读之前 ②解锁在加锁之前 
        // 4. 即使第一时间获取到的是新值,但是并发环境下,可能刚获取完,就被更新了
        //    (先读后写了);弱一致性,体现在这
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

没有加锁操作,弱一致性换取性能

put()

1. Segment -> put()

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 尝试获取锁,获取成功则 node = null;
    // 获取失败,则进入 scanAndLockForPut 方法,自旋获取锁
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    // 因为加了锁,同一时刻都只有一个线程执行下面的操作;
    // 并且解锁前,数据都会同步到主存,所以无需使用 volatile 修饰变量;
    // 减小 volatile 对性能的影响
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        // --- 标记 ---
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                // 表示其他线程在自旋过程中,已经创建了新结点;
                // 得到锁时,不需要再创建一遍,减少了锁的占用时间
                if (node != null)
                    // 将 node 使用头插法
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                // 先判断是否扩容,再插入新结点;
                // HashMap 是先插入,再判断扩容,可能导致扩容后再无新结点插入;
                // 造成无效扩容
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    // 已经加锁,确保扩容安全
                    rehash(node);
                else
                    // --- 标记 ---
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        // 解锁
        unlock();
    }
    return oldValue;
}

2. Segment -> scanAndLockForPut()

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
    int retries = -1; // negative while locating node
    // 获取锁失败,则遍历链表,将遍历过的 HashEntry 放入 CPU 高速缓存中;
    // 获取锁之后,再次定位速度会十分快,等待过程中的一种预热方式
    while (!tryLock()) {
        HashEntry<K,V> f; // to recheck first below
        // 首次进入,retries = -1 恒成立
        if (retries < 0) {
            // e = null 的原因:①HashEntry[] 对应位置为 null②遍历表到最后,没有指定 key
            if (e == null) {
                // 再次进入循环,避免重复符值
                if (node == null) // speculatively create node
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            // 找到指定 key
            else if (key.equals(e.key))
                retries = 0;
            else
                e = e.next;
        }
        // 自旋次数超过最大次数,则直接进入阻塞;
        // 无限制的自旋,性能损耗比阻塞更大
        else if (++retries > MAX_SCAN_RETRIES) {
            lock();
            break;
        }
        // 遍历过程中,发现链表已经被改变,则重新进行遍历
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}

rehash()

private void rehash(HashEntry<K,V> node) {
    HashEntry<K,V>[] oldTable = table;
    int oldCapacity = oldTable.length;
    int newCapacity = oldCapacity << 1;
    threshold = (int)(newCapacity * loadFactor);
    HashEntry<K,V>[] newTable =
        (HashEntry<K,V>[]) new HashEntry[newCapacity];
    // 新掩码,比原来的多一位 1;00001111 -> 00011111
    int sizeMask = newCapacity - 1;
    for (int i = 0; i < oldCapacity ; i++) {
        HashEntry<K,V> e = oldTable[i];
        if (e != null) {
            HashEntry<K,V> next = e.next;
            // 生成新下标
            int idx = e.hash & sizeMask;
            // 没有后继节点,只有单个节点
            if (next == null)   //  Single node on list 
                // 新数组可以直接引用
                newTable[idx] = e;
            else { 
                // 非单节点
                // Reuse consecutive sequence at same slot
                HashEntry<K,V> lastRun = e;
                int lastIdx = idx;
                for (HashEntry<K,V> last = next;
                     last != null;
                     last = last.next) {
                    int k = last.hash & sizeMask;
                    if (k != lastIdx) {
                        lastIdx = k;
                        // 第一个后续所有节点在新数组中下标保持不变的节点 lastRun
                        lastRun = last;
                    }
                }
                //这个for循环就是找到第一个后续节点新的index不变的节点。
                newTable[lastIdx] = lastRun;
                // Clone remaining nodes
                //lastRun 之前的结点均需复制一遍
                for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                    int h = p.hash;
                    // 生成新下标
                    int k = h & sizeMask;
                    // 头插法
                    HashEntry<K,V> n = newTable[k];
                    newTable[k] = new HashEntry<K,V>(h, p.key, p.value, n);
                }
            }
        }
    }
    // 为新加入的节点,在新数组生成下标
    int nodeIndex = node.hash & sizeMask; // add the new node
    // 头插法
    node.setNext(newTable[nodeIndex]);
    // 新数组下标指向新节点
    newTable[nodeIndex] = node;
    // 赋值给原数组
    table = newTable;
}
  1. put 时,会判断 Segment 里的 HashEntry 数量是否超过阈值(threshold);超过则进行扩容。
  2. rehash() 是没有加锁的,因为外层已经加了锁,确保是单线程操作。
  3. 过程:新建容量为原来两倍的新数组;每个桶,找到第一个后续所有节点在新数组中下标保持不变的节点 lastRun,新数组可直接引用 lastRun,之前的结点全部需要复制。

size() 

public int size() {
    final Segment<K,V>[] segments = this.segments;
    // HashEntry 数量
    int size;
    // 是否溢出
    boolean overflow;
    // 本次的 modcount 值
    long sum;
    // 上次的 modcount 值
    long last = 0L;
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            // 当 retries = RETRIES_BEFORE_LOCK 时,全部 Segment 加锁
            // 会导致本来延迟加载的 Segment 全部创建
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    // ensureSegment 确保该 Segment 已经创建;
                    // segments[0] 为模板,懒加载其他结点
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    // 本轮 modcount
                    sum += seg.modCount;
                    // 本 Segment 元素数量
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            // modcount 没变,则计算结果准确
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        // 只有当 retries = RETRIES_BEFORE_LOCK 时,才加锁;防止未加锁,进行解锁
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

1.8

特点 

  1. 使用 synchronized + CAS 保证并发安全。 
  2. 发生哈希冲突,链表 + 红黑树。
  3. 扩容时,多个线程分摊进行,提高效率(多核)。

重要属性

// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认容量
static final int DEFAULT_CAPACITY = 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;
// 最大帮助线程数量
static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 起始扩容线程标志
static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// 已经完成迁移的桶的标志
static final int MOVED     = -1;
// CPU 核数
static final int NCPU = Runtime.getRuntime().availableProcessors();
// 当前节点数量
transient volatile long baseCount;
// 扩容阈值
transient volatile int sizeCtl;
// 下一个需要迁移的区间的起始位置
transient volatile int transferIndex;
// 计数单元表,用于合并计算总的结点数
transient volatile CounterCell[] counterCells;

 

addCount()

// 真正 put 进新元素之后,需要执行的操作。
private final void addCount(long x, int check) {
    //⭐减少自旋对性能的损耗:
    //   1.1 所有线程都尝试修改 baseCount,可能会导致大量的自旋;
    //   1.2 将修改操作分散:线程对应 CounterCell[] 中一个元素,进行修改,减少自旋;
    //   1.3 最后的 sumCount() 是将 CounterCell[] 所有结果相加,得到最终结果。
    CounterCell[] as; long b, s;
    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;
        // 此时 sizeCtl 还是代表阈值;
        // 扩容开始后会变成负值,依旧成立;
        // ⭐重要作用:线程迁移完自己所负责的区域之后,发现扩容还在进行,则继续帮助扩容
        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;
                // 加入扩容,扩容线程数 + 1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 扩容还未开始,当前线程开启扩容,初始化新数组
            // (rs << RESIZE_STAMP_SHIFT) + 2) 为特定值,用于判断是否为第一个线程
            // CAS 操作确保只有一个线程开启扩容
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            // 获取最终结果
            s = sumCount();
        }
    }
}

tryPresize()

treeifyBin() -> tryPresize():真正树化前,需要判断当前节点总数。

putAll() -> tryPresize():批量添加前,会检查是否需要扩容。

// 1. 批量插入,调用该方法。
// 2. 链表节点数 >= 8,但是节点总数 < 64,调用该方法。
private final void tryPresize(int size) {
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
        tableSizeFor(size + (size >>> 1) + 1);
    int sc;
    // 为负数,表示在初始化或扩容
    while ((sc = sizeCtl) >= 0) {
        Node<K,V>[] tab = table; int n;
        // 数组还未初始化
        if (tab == null || (n = tab.length) == 0) {
            n = (sc > c) ? sc : c;
            // 初始化时,sizeCtl 设为 -1。
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if (table == tab) {
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = nt;
                        // 阈值只占真实容量的 3/4。
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
            }
        }
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        // 判断扩容
        else if (tab == table) {
            int rs = resizeStamp(n);
                        .
            //---- 与 addCount() 相同的扩容判断。
                        .
        }
    }
}

helpTransfer() 

扩容时,调用插入、删除、合并等修改操作,遇到 ForwardingNode 节点(hash = -1),该线程会进行帮助扩容。 

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

transfer 

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    int n = tab.length, stride;
    // stride:每个线程负责多少个桶。
    // 单核则负责全部桶,每个线程至少负责 16 个桶。
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE;
    // 第一个线程开启扩容,初始化新数组。
    if (nextTab == null) {
        try {
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        transferIndex = n;
    }
    int nextn = nextTab.length;
    // 新建一个占位节点,放在桶的首部;
    //     1.该桶已经迁移完毕,整体还处于扩容状态。
    //     2.当执行 get() 时,转发至新数组。 
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    // 单纯用作循环条件,循环内部计算下一个桶的位置。
    boolean advance = true;
    // 代表整个迁移工作是否完成。
    boolean finishing = false;
    // bound:线程负责区域左边界;为长度为 stride 区间的左边界。
    // nextIndex:线程负责区域右边界;为长度为 stride 区间的右边界。
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        while (advance) {
            int nextIndex, nextBound;
            // 每次处理完一个桶,i 就左移
            if (--i >= bound || finishing)
                advance = false;
            // 分配的起始位置 <= 0,表示扩容已经完成
            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;
            }
            // 扩容线程数量 - 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
            }
        }
        // 当前桶为空,插入占位节点
        else if ((f = tabAt(tab, i)) == null)
            advance = casTabAt(tab, i, null, fwd);
        // 当前桶已经迁移完成
        else if ((fh = f.hash) == MOVED)
            advance = true;
        else {
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    Node<K,V> ln, hn;
                    if (fh >= 0) {
                        int runBit = fh & n;
                        // 确定最后一个位发生变化的 key
                        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;
                            }
                        }
                        if (runBit == 0) {
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                            hn = lastRun;
                            ln = null;
                        }
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            // 采取头插法,lastRun 在后面
                            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);
                        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;
                    }
                }
            }
        }
    }
}

过程简述:

  1. 计算每个线程负责迁移的桶的数量。
  2. 初始化新数组,新建占位节点。
  3. 计算出当前线程负责的具体区域。
  4. 开始将每个桶的元素迁移至新数组。
  5. 迁移当前区域完成,迁移下一个区域。
  6. 整体迁移完成,再从后往前扫描一遍,查看是有遗漏。

  • 对于获取,没有加锁,不会阻塞。
  • 对于更新,每个桶开始迁移前需要加锁,更新操作也都需要加锁,会阻塞。 

迁移设计:

  • 某些 key 高位为 1,扩容后可以发挥用处;hash & n = 1 的 key 在新数组中位置为 n + i;与 hash & (n ^ 2 - 1) 相等,该位置正确。
  • 采用头插法;利用高位链与低位链,将桶中 key 分开;使用 lastRun 确定最后一个位置发生变化的 key(从它开始,后面的 key 都处于高位或低位),这些 key 可以不再复制;极端情况整条链都处于同位,全部无需复制。
  • 红黑树采用链表式遍历,执行相同操作,最后再判断是否树化。
MIKE 21 教程 2.9 初始条件(Initial Conditions),边界条件Boundary Conditions),解耦(Decoupling) 本节讲解初始条件与边界条件的设置。这两个模块如设置有误则极易出现模型崩溃。 阅读详情

相关推荐

GEO芯片数据下载和探针ID转换(保姆级教程)

R语言安装、GEO芯片数据下载、探针ID转换

qq_43138237的博客 3万+

java并发编程ConcurrentHashMap(1.7分段锁和1.8CAS+同步锁)扩容机制补充

东西很多,我就按照其他博客进行分类整理 1.7锁分段技术 https://blog.csdn.net/yansong_8686/article/details/50664351 结构如下,锁分段成数组,每个分段下面存kv Segment是可重入锁 哈希分散后能够更均匀的分配到各个Segment上 get不加锁,很快,关键在于volatile保证可见性,但不保证原子性 put对分段Segment加锁,Segment提高效率的地方就在于操作时不需要对整个hashMap都加锁。之后和普通map一样都要考虑

Koikoi12的博客 2400

秋叶ComfyUI-v10中文整合包:全中文AI绘画一键部署与实战指南

Stable Diffusion作为当前主流的扩散模型,通过去噪过程从随机噪声生成高质量图像,其开源特性推动了AI绘画技术的普及。然而,其官方WebUI在自定义工作流和可复现性上存在局限,而基于节点流程的ComfyUI则提供了更高的自由度和工程化控制能力,尤其适合复杂、可重复的创作流程。对于中文用户而言,英文界面和提示词输入是主要障碍。秋叶出品的ComfyUI-v10中文整合包,正是针对这一痛点,提供了**全中文界面**和**原生中文提示词支持**的解决方案。该整合包将Python环境、依赖库及常用插件预配

weixin_33895475的博客 399

ConcurrentHashMap的扩容方法详解

一、ConcurrentHashMap扩容 (jdk1.7) 扩容只针对ConcurrentHashMap中的hashEntry类型的table数组进行扩容。 方法在Segment类中:rehash() , 扩容倍数为:2倍扩容 源码解析: private void rehash(HashEntry&lt;K,V&gt; node) { //先将需要扩容的table数组进行拷贝 ...

Jewel’s blog 9751

H3C无线控制器AC与第三方认证系统集成实现企业级Portal短信认证方案

本文详细阐述了如何将H3C无线控制器(AC)与第三方认证系统集成,构建企业级Portal短信认证方案。该方案通过手机号作为临时凭证,实现了访客网络的实名追溯、权限控制与专业体验。文章深入解析了方案原理、H3C AC的关键配置步骤、第三方系统对接要点及短信网关选型避坑指南,为企业提升网络安全管理与访客服务质量提供了完整、可行的实战指导。

a3b4c5的博客 194

智能体开发套件(ADK)介绍

city } ---") city_normalized = city . lower() . replace(" " , "") # 模拟天气数据库 mock_weather_db = {

lpfasd123的博客 1401

Java基础 -> 谈谈ConcurrentHashMap的扩容机制

文章目录谈谈ConcurrentHashMap的扩容机制 谈谈ConcurrentHashMap的扩容机制 1.7版本 -&gt; 理解即可 1.7版本的ConcurrentHashMap是基于Segment分段实现的 每个Segment相对于⼀个⼩型的HashMap 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似 先⽣成新的数组,然后转移元素到新数组中 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值 1.8版本 扩容相关的属性:

rod0320的博客 5013

Windows 11 安装 Maven:下载、安装、配置国内镜像、验证

本文详细介绍了在 Windows 11 系统中安装与配置 Apache Maven 的完整流程,包括 JDK 环境准备、Maven 下载解压、本地仓库路径设置、阿里云国内镜像加速配置、环境变量设置及安装验证。同时提供了常用 Maven 命令速查表,帮助开发者快速上手依赖管理与项目构建,提升开发效率。

寒水馨 · 轻量级技术文档 · hanshuixin.org 1502

JAVA面试考点——ConcurrentHashMap原理以及扩容机制

目录 1.7版本 1.8版本 1.7版本 1.7版本底层示例 1. ConcurrentHashMap是基于Segment分段实现的 2.每个Segment相对于一个小型的HashMap 3. 扩容时,对待扩容Segment内部会进行扩容,不影响其他Segment对象 4. 扩容时,先生成新的数组,然后转移元素到新数组中 5. 扩容的判断也是每个Seg...

一直走,不要回头 3539

15张图详解三方支付:总架构、5大核心业务处理流程、备付金管理

用户可以将银行卡与零钱账户绑定,建立银行结算账户与支付账户的连接,然后将银行账户资金充值到零钱账户中,或将零钱账号中的余额提现至绑定的银行卡。因此,用户在A支付机构的零钱一般只能支付给A支付机构的商户,如支付宝的零钱不能支付给微信的商户。这五大业务的协同运作,使得第三方支付机构这一庞大的支付服务平台能够为市场提供卓越的支付能力。到了结算时间,网联会向备付金集中存管账户提交结算指令,在第三方支付机构备付金集中存管账户内完成最终的结算,并将结算结果及额度变动情况通知给第三方支付机构,具体流程如图13所示。

银行信息系统应用架构详解 1044

在 ABAP 平台上搭建 OAuth 2.0 Server:架构、流程、权限与落地实战

摘要:本文为SAP ABAP开发人员提供将ABAP平台配置为OAuth 2.0授权服务器的实战指南。重点介绍两种授权流程(授权码和SAML Bearer)的实现方案,包括核心事务配置(SOAUTH2/SAML2)、授权对象(S_SCOPE)管理以及端点交互规范。通过具体配置清单和三类典型集成场景(第三方Web应用访问OData、企业集成平台调用、BTP应用对接)的落地示例,帮助读者快速构建安全的OAuth 2.0服务架构。文档强调SSL/TLS基础安全、scope最小授权原则以及授权与令牌端点的标准交互模式

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 308

JDK1.7 ConcurrentHashMap源码分析

JDK1.7 ConcurrentHashMap源码分析 ConcurrentHashMap的基本策略是将table细分为多个Segment保存在数组segments中,每个Segment本身又是一个可并发的哈希表,同时每个Segment都是一把ReentrantLock锁,只有在同一个Segment内才存在竞态关系,不同的Segment之间没有锁竞争,这就是分段锁机制。Segment内部拥有一个HashEntry数组,数组中的每个元素又是一个链表。 为了减少占用空间,除了第一个Segment之外,剩余的S

qq_41092912的博客 480

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ConcurrentHashMap底层实现原理((JDK1.7&1.8))

目录前言ConcurrentHashMap (JDK1.7的实现)初始化put操作get操作size操作ConcurrentHashMap (JDK1.8的实现)NodeTreeNodeTreeBinput操作get操作size操作总结与思考 前言 我们都知道HashMap在多线程情况下,在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原...

CoderZS的博客 1万+

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

JDK1.7&1.8中ConcurrentHashMap解析

一.总体概述 HashMap在开发中很常用,但HashMap存在一个弊端就是线程不安全,解决办法就是使用Hashtable代替或使用Collections.synchronizedMap(m);将HashMap转换为线程安全的,但这两种方法虽说实现了线程安全,但是并发性能比较差,因为是全表加锁,那么并发界的大牛Doug Lea就为我们提供了ConcurrentHashMap解决这个问题,不仅实现...

qq_36625757的博客 1050

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

ConcurrentHashMap底层原理(1.7&1.8)

为了解决HashMap的线程不安全问题,我们大多采用CHM

lans_g的博客 2083

Java ConcurrentHashMap底层实现原理(JDK1.7&1.8)【总结】

为了彻底搞清楚ConcurrentHashMap的实现机制,我会先从它的底层数据实现:哈希表谈起 一、哈希表:原链接 1、介绍:哈希表就是一种以键-值(key-value)存储数据的结构,我们只要输入带查找的值即key,即可查找到其对应的值。 哈希表的思路很简单,如果所有的键都是整数,那么久可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值,这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。 2、链式哈希表:链式哈希表从根本上说是由一组链表构成。每

单枪匹码 2996

ConcurrentHashMap源码详解(JDK1.7&1.8)

ConcurrentHashMap源码分析,ConcurrentHashMap1.7源码,ConcurrentHashMap1.8源码,ConcurrentHashMap线程安全

萌萌的PP博客 833

ConcurrentHashMap底层实现原理(JDK1.7 & 1.8)

ConcurrentHashMap底层实现原理(JDK1.7 &amp; 1.8) 前言 我们都知道HashMap在多线程情况下,在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。 我们来了解另一个键值存储集合HashTable

jiohfgj的博客 604

HashMap(1.8)与ConcurrentHashMap(1.7&1.8)详解

1.HashMap(基于jdk1.8) 1.1、HashMap(基于jdk1.8)的存储结构 jdk1.8中,HashMap的存储结构为:数组(哈希表)+链表+红黑树(jdk1.7中HashMap的存储结构为数组(哈希表)+链表) jdk1.8中HashMap的结构图: 1.2、HashMap(基于jdk1.8)的源码分析 public class HashMap&lt;K,V&gt; extends AbstractMap&lt;K,V&gt; implements Map&lt;K,V&gt;, Cl

wang_luwei的博客 1051

ConcurrentHashMap源码学习笔记jdk1.7&1.8

(一)基于JDK1.7的ConcurrentHashMap 1.基本实现: 由Segement数组和HashEntry组成,与HashMap相同都是数组+链表的结构,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样 Segement: ...

weixin_42173193的博客 245

JDK1.7 & 1.8 的 HashMap & ConcurrentHashMap 源码/线程安全 全解析

这个专题的由来 笔者在工作过程中面试过很多初中级的同学,很少有人能完完全全的理解JDK中的HashMap 和 ConcurrentHashMap,为什么面试会去问这些,我觉得这个部分属于Java的一些高级特性,可以看出应聘者的技术深度 (俗称面试造火箭嘛) 网上有很多博文写的要么不全面,要么不够深入,特别是线程安全部分,我几乎没有看见过分析的全面的文章,我决定写一篇全面而有深度的文章送给...

dhwkjr1467的博客 436

Java集合:ConcurrentHashMap(JDK 1.7 & JDK 1.8)

ConcurrentHashMap(JDK 1.7) HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,...

五山口老法师 732

ConcurrentHashMap 1.7&1.8异同分析简介及部分源码分析

ConcurrentHashMap 1.7 概述 特点 是线程安全的 。 采用分段锁的形式加锁,对每个**Segment**进行加锁保证局部线程安全从而保证全局线程安全 由于是对每个**Segmeng**进行加锁,所以并发数受到**Segmeng**数组长度影响 **Segmeng**数组不可扩容,**Segmeng**内部可扩容 **Segment**内部采用数组加链表进行存储(可近似的看成一个无红黑树的**HashMap**,不过他是线程安全的) 存储结构 整个 ConcurrentHashMa

xzystart的博客 770

Java ConcurrentHashMap底层实现原理(JDK1.7&1.8)【源码分析】

我们都知道HashMap在多线程情况下,在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致get时会出现死循环,所以HashMap是线程不安全的。 我们来了解另一个键值存储集合HashTable,它是线程安全的,它在所有涉及到多线程操作的都加上了synchronized关键字来锁住整个

单枪匹码 1364

JDK(十)JDK1.7&1.8源码对比分析【集合】ConcurrentHashMap

前言 在JDK1.7&amp;1.8源码对比分析【集合】HashMap中我们对比分析了JDK1.7和1.8版本的HashMap源码,趁热打铁,这篇文章就来看看JDK1.7和1.8版本的ConcurrentHashMap有哪些区别。 目录 一、对比分析 1. 1.7版本 2. 1.8版本 一、对比分析 1. 1.7版本 先来看看 1.7 的实现,下面是他的结构图: 如...

dlx29376的博客 158

ConcurrentHashMap & HashMap最清晰的底层原理分析(基于JDK1.7跟1.8比较)

前言 Map 这样的 Key  Value 在软件开发中是非常经典的结构,常用于在内存中存放数据。 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 HashMap,没有它就不会有后面的 ConcurrentHashMap。 HashMap 众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中...

zxd8080666的博客 827
上一篇: 运输层(三)—— TCP 三次握手 & 四次挥手
下一篇: MySQL基础篇(一)查询 & 更新
皮皮杨233
博客等级 码龄7年 42粉丝 75原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值