Java 7 及之前的版本
ConcurrentHashMap 中的 size()方法的工作机制旨在提供一个尽可能准确的元素数量估计,即使在多线程同时对这个集合进行修改的情况下。以下是它的基本工作原理:
  1. 遍历 Segment:在 Java 7 及之前的版本中,ConcurrentHashMap 是基于分段锁(Segment)实现的。每个 Segment 独立地维护了一部分映射的元素,并且有自己的锁。调用 size()方法时,会遍历所有的 Segment。

  2. 累加计数器:每个 Segment 维护了一个 count 变量,表示该 Segment 的元素数量。size()方法将通过遍历所有 Segment,累加这些 count 变量来得到总大小。
  3. 重试机制:因为其他线程可能正在并发修改 ConcurrentHashMap,所以 size 计算过程中可能会出现不一致。为了解决这个问题,初次计算后,如果检测到结构性修改,会重新尝试获取大小。
  4. 返回大小:如果没有检测到并发修改,则返回累加后的大小;如果在尝试了若干次(通常是三次)后仍然检测到并发修改,则放弃并返回当前累加值。

Java 8 及之后的版本

在 Java 8 及之后的版本中,ConcurrentHashMap 的实现摒弃了 Segment 的概念,转而使用了 Node 数组加链表和红黑树的数据结构。这里是 Java 8 中 ConcurrentHashMap 的 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;
}

在这个代码中:

  • baseCount 是一个长整型变量,用于在没有竞争时快速统计。

  • counterCells 是一个 CounterCell 数组,每个 cell 包含一个值,在多线程环境下,每个线程可以操作数组中不同的 cell 来减少争用。
  • sumCount()方法遍历所有的 cells 来累加计数,加上 baseCount 以得到总的元素数。

准确性

size()方法返回的结果通常是近似值,而不是绝对准确的:

  • 并发更新:如果在执行size()方法的同时有其他线程在修改ConcurrentHashMap,则返回的大小可能与实际大小不符。

  • 最终一致性:尽管存在并发更新问题,但在没有线程活动修改映射时调用size(),或者在所有线程都看到映射状态一致时调用size(),它将返回准确的大小。
  • 越界检查:size()方法会检查计算出的大小,如果超过Integer.MAX_VALUE,则返回Integer.MAX_VALUE。这意味着极大的ConcurrentHashMap实例的大小将被截断。
总结:由于ConcurrentHashMap是为高并发设计的,其设计目标并不是在任何时刻都提供完美准确的大小计数,而是保证数据结构的完整性和线程安全,并在此基础上尽可能地提供正确的大小信息。因此,在高并发场景下,size()方法的结果应该被视为一个估计,而不是一个精确值

本篇文章来源于微信公众号: 互联网面试小帮手



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-08-02