并发取最大值不可直接用普通堆或简单锁,应选AtomicInteger/CAS、LongAdder、ConcurrentSkipListSet或无锁环形缓冲等线程安全方案,需据实时性、数据量和系统链路权衡。
并发环境下直接用普通变量或简单锁机制维护“最大值”不可靠,也低效。真正可行的方案是借助线程安全的数据结构或原子操作逻辑,核心矛盾在于:既要保证读写一致性,又要避免高竞争下的性能塌方。堆结构(如最大堆)本身不天然支持并发更新,强行加锁会导致串行化,失去并发意义;所以实际生产中更倾向组合方案——用并发友好的基础计数/比较工具 + 业务层逻辑兜底。
为什么并发取最大值不能直接用普通堆
标准的PriorityQueue(基于数组的最大堆)在Java中是非线程安全的。多线程同时offer()或poll()会引发数据错乱、索引越界甚至无限循环。即使包装成Collection.synchronizedList,也只是对单个方法加锁,无法保障“读-改-写”这类复合操作的原子性(例如“如果新值大于当前最大值,则更新”)。更关键的是,锁粒度大,高并发下线程频繁阻塞,吞吐量断崖式下降。
推荐替代方案与压测表现对比
以下为JDK 1.8+主流方案在1000+线程高频更新场景下的实测倾向(基于相同硬件与JVM参数):
-
AtomicInteger / AtomicLong + CAS轮询更新:适合整型最大值场景。用
getAndUpdate(x -> Math.max(x, newValue))或手动CAS循环。优势是无锁、响应快;劣势是极端高并发时CAS失败率升高,CPU空转增加。压测显示:500线程内稳定,超1000线程后P99延迟开始上扬。 -
LongAdder + 定期快照取max:不适用于实时最大值,但适合“统计窗口内最大观测值”。将每次上报值写入
LongAdder的某个cell(按线程哈希分散),再由监控线程定期遍历所有cell取max。吞吐量比Atomic高3–5倍,但最大值有秒级延迟。 -
ConcurrentSkipListSet(升序) + last():插入用
add(),取最大值调last()。跳表本身线程安全,last()是O(log n)但常数小。实测在万级元素、百线程下P95 -
自定义无锁环形缓冲区 + 原子指针:固定大小缓冲区(如1024槽),每个写线程用
getAndIncrement获取唯一索引,写入后由专用消费者线程扫描全缓冲取max。适合对最大值时效性要求不高(容忍几十毫秒延迟)、但吞吐必须极致的场景。
选型关键判断点
不用纠结“哪个结构最强”,而要看你的具体约束:
- 是否需要强实时性?若要求任意时刻读到的都是最新最大值,优先Atomic+CAS;若可接受100ms–1s延迟,LongAdder或环形缓冲更稳。
- 数据量级是否可控?若最大值候选集长期ConcurrentSkipListSet简洁可靠;若每秒新增数万候选值,需考虑内存增长和GC压力。
- 是否已有监控/聚合链路?如果系统已用Micrometer+Prometheus采集指标,可直接用
Gauge暴露当前最大值,由外部定时拉取并更新,业务代码完全无并发负担。
不复杂但容易忽略:多数场景下,“最大值”本质是监控信号而非业务状态。与其花大力气做高并发精确维护,不如评估是否可用采样(如每100次更新只记录一次)、降精度(如按10为单位取整)或异步聚合来换取稳定性。










