You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ConcurrentSkipListMap存在数据时调用firstKey()仍抛出NoSuchElementException求助

问题根因分析
  • 竞态条件导致调用瞬间Map为空:ConcurrentSkipListMap仅保证单个操作的原子性,无法保障多线程组合操作的原子性。你的应用存在两个并行操作Map的线程:WebSocket推送线程负责更新、删除Map元素,独立循环线程负责读取Map首个元素。当WebSocket线程刚好将Map内所有元素删除(比如深度更新将所有现有价格档位的交易量置0,还未写入新价格档位)的瞬间,循环线程恰好调用firstKey()/firstEntry(),就会触发NoSuchElementException,哪怕后续毫秒级就有新元素写入Map,也不影响调用瞬间抛出异常。
  • 裁剪Map的逻辑存在隐忧:你在裁剪Map长度时使用StaticData.ask.keySet().toArray()[10]获取第11个元素的key,哪怕已经提前判断size()>10,但判断和toArray是两个独立操作,执行toArray时如果刚好有元素被删除导致size不足11,会触发数组越界异常,极端情况下甚至可能在裁剪时误删所有元素,进一步触发首元素读取异常。
  • 静态变量可见性风险:StaticData.ask未加volatile修饰,存在指令重排序导致循环线程拿到空引用的风险,初始化逻辑如果触发异常未重试,也会导致ask一直为空。
修复方案
  1. 安全读取首元素:不要直接调用firstKey()/firstEntry(),如果不需要移除元素可以在调用时捕获NoSuchElementException后跳过本次循环,如果允许移除元素可以改用pollFirstEntry()读取,返回null直接跳过本次循环即可,示例代码如下:
public void loopProcess() {
    Analyzer analyzer = new Analyzer();
    while (true) {
        Map.Entry<Double, Double> entry;
        try {
            entry = StaticData.ask.firstEntry();
        } catch (NoSuchElementException e) {
            // 为空则短暂休眠降低CPU占用,跳过本次分析
            Thread.sleep(1);
            continue;
        }
        analyzer.analyze(entry);
    }
}
  1. 优化Map裁剪逻辑:改用更安全的方式裁剪,避免数组越界和误删所有元素,示例如下:
// 裁剪到只剩10个元素
while (StaticData.ask.size() > 10) {
    StaticData.ask.pollLastEntry();
}
  1. 完善静态变量定义:给StaticData.ask添加volatile修饰,保证多线程可见性;初始化initAsks逻辑添加失败重试,确保ask成功初始化后再启动循环线程。

内容的提问来源于stack exchange,提问作者Tomáš Sýkora

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 15:18:03