ConcurrentHashmap size 1.8方法实现

本文详细解析了Java 1.8中ConcurrentHashMap的size()方法实现原理。通过使用volatile变量baseCount记录元素个数,以及CounterCell数组分散并发操作,实现了高效且线程安全的元素计数。在高并发场景下,通过CAS操作和CounterCell对象动态调整计数策略,确保了数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.8中使用一个volatile类型的变量baseCount记录元素的个数,当插入新数据或则删除数据时,会通过addCount()方法更新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();
}

1、初始化时counterCells为空,在并发量很高时,如果存在两个线程同时执行CAS修改baseCount值,则失败的线程会继续执行方法体中的逻辑,使用CounterCell记录元素个数的变化;

2、如果CounterCell数组counterCells为空,调用fullAddCount()方法进行初始化,并插入对应的记录数,通过CAS设置cellsBusy字段,只有设置成功的线程才能初始化CounterCell数组,实现如下:

else if (cellsBusy == 0 && counterCells == as &&
         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
    boolean init = false;
    try {                           // Initialize table
        if (counterCells == as) {
            CounterCell[] rs = new CounterCell[2];
            rs[h & 1] = new CounterCell(x);
            counterCells = rs;
            init = true;
        }
    } finally {
        cellsBusy = 0;
    }
    if (init)
        break;
}

3、如果通过CAS设置cellsBusy字段失败的话,则继续尝试通过CAS修改baseCount字段,如果修改baseCount字段成功的话,就退出循环,否则继续循环插入CounterCell对象;

else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
    break;

所以在1.8中的size实现比1.7简单多,因为元素个数保存baseCount中,部分元素的变化个数保存在CounterCell数组中,实现如下:

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

通过累加baseCount和CounterCell数组中的数量,即可得到元素的总个数;

<think>嗯,用户问的是ConcurrentHashMap在JDK 1.7和1.8之间的区别。我得先回忆一下这两个版本的不同实现方式。记得ConcurrentHashMap是为了解决HashMap线程不安全的问题,同时相比Hashtable有更好的性能。 首先,JDK 1.7的时候,ConcurrentHashMap采用的是分段锁,也就是Segment的结构。每个Segment相当于一个小的HashTable,这样不同的Segment之间的操作可以并发进行,减少了锁的竞争。而到了1.8,可能改成了和HashMap类似的数组+链表+红黑树的结构,同时使用CAS和synchronized来保证线程安全。这里需要确认一下具体的实现变化。 然后是锁的机制。1.7的分段锁,每个Segment继承自ReentrantLock,这样在put操作时只需要锁住对应的Segment,而其他Segment不受影响。而1.8可能摒弃了分段锁,改为对每个数组元素(Node)使用synchronized和CAS操作,这样锁的粒度更小,并发度更高。需要验证一下这个点是否正确。 数据结构方面,1.8应该引入了红黑树,当链表长度超过一定阈值时,会转换成红黑树,这样查询效率更高。而1.7可能还是单纯的链表结构。这需要确认两个版本在数据结构上的差异。 线程安全的实现方式,1.7使用分段锁,而1.8使用CAS和synchronized。CAS用于无锁化的读和部分写操作,而synchronized在节点上加锁,这样减少了锁的竞争,提高了并发性能。这里需要详细说明两者的具体实现方式。 另外,size方法实现可能也有不同。在1.7中,可能需要统计各个Segment的大小然后求和,而1.8可能采用了更高效的方法,比如基础计数器或辅助计数器,通过CAS来维护。这一点也需要比较说明。 还有迭代器的弱一致性问题,1.7和1.8都是弱一致性的,但具体的实现细节是否有变化?可能需要确认是否在结构上有调整导致迭代器的行为不同。 还有哈希碰撞的处理,1.8的链表转红黑树可以减少查询时间,这在并发情况下可能更高效。而1.7的链表在冲突多时查询效率低。 需要组织这些点,结构清晰,分点说明两者的区别,同时确保技术细节准确。例如,分段锁的具体结构,CAS和synchronized的应用场景,数据结构的改变带来的影响等。 可能还需要提到1.8中为什么放弃分段锁,比如因为分段锁的内存消耗较大,而在实际应用中并发级别设置不合理可能导致性能问题。而1.8的锁粒度更细,每个节点单独加锁,提高了并发度。 总结的话,需要从数据结构、锁机制、并发度、hash冲突处理、size计算等方面进行对比,确保覆盖用户关心的主要差异点,并且用通俗易懂的语言解释清楚。</think>ConcurrentHashMap 在 JDK 1.7 和 1.8实现差异主要体现在**数据结构、锁机制和并发策略**上。以下是核心区别的对比分析: --- ### **一、数据结构差异** 1. **JDK 1.7** - 使用 **Segment 分段锁 + HashEntry 数组 + 链表**。 - 将整个哈希表划分为多个 **Segment**(默认 16 个),每个 Segment 独立加锁,相当于一个线程安全的 HashMap。 - 代码示例: ```java static final class Segment<K,V> extends ReentrantLock { transient volatile HashEntry<K,V>[] table; } ``` 2. **JDK 1.8** - 改用 **Node 数组 + 链表/红黑树**,与 HashMap 结构一致。 - 当链表长度超过阈值(默认 8)时,转换为红黑树,降低查询时间复杂度为 $O(\log n)$。 - 代码示例: ```java transient volatile Node<K,V>[] table; static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; } ``` --- ### **二、锁机制对比** 1. **JDK 1.7 的分段锁** - 通过 **ReentrantLock 锁住 Segment**,不同 Segment 互不影响。 - 写操作需要锁住当前 Segment,读操作无需加锁(通过 `volatile` 保证可见性)。 - **缺点**:锁粒度较粗(锁定整个 Segment),并发度受限于 Segment 数量。 2. **JDK 1.8 的细粒度锁** - 使用 **CAS + synchronized 锁单个 Node**(链表头节点或红黑树根节点)。 - 写操作仅锁住当前链表或树的头节点,其他位置的读写仍可并发。 - **优点**:锁粒度更细,并发度更高(与哈希表大小相关)。 --- ### **三、并发性能优化** 1. **JDK 1.8 的改进** - **CAS 无锁化操作**:如 `put` 操作中通过 CAS 初始化数组或插入头节点。 - **扩容优化**:支持多线程协同扩容(通过 `ForwardingNode` 标记迁移状态)。 - **计数器优化**:使用 `LongAdder` 思想统计元素数量,减少竞争。 --- ### **四、其他关键区别** | **特性** | **JDK 1.7** | **JDK 1.8** | |----------------------|--------------------------------------|--------------------------------------| | **锁范围** | 锁住整个 Segment | 锁住单个 Node 或树的根节点 | | **Hash 冲突处理** | 链表 | 链表 + 红黑树 | | **并发度** | 受限于 Segment 数量(默认 16) | 理论上与哈希表大小一致 | | **内存占用** | 较高(Segment 数组 + HashEntry 数组)| 较低(仅 Node 数组) | --- ### **五、代码行为差异** 1. **`size()` 方法实现** - **JDK 1.7**:遍历所有 Segment 的 `count` 求和,尝试多次直到结果一致。 - **JDK 1.8**:直接返回 `baseCount` 与 `CounterCell` 的累加值(类似 `LongAdder`)。 2. **迭代器弱一致性** - 两个版本均为弱一致性迭代器,但 1.8 因红黑树结构可能在遍历时看到部分更新。 --- ### **总结** JDK 1.8ConcurrentHashMap 通过 **更细粒度的锁、CAS 操作和红黑树优化**,显著提升了并发性能和扩展性,同时减少了内存开销。而 JDK 1.7 的分段锁设计在极高并发场景下容易成为瓶颈。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值