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

ConcurrentSkipListSet弱一致性异常的原因与解决方案咨询

ConcurrentSkipListSet元素丢失问题:原因分析与解决方案

首先,咱们一步步拆解你的问题,排查核心原因:

一、你的现象是否由弱一致性迭代器导致?

大概率不是——先明确ConcurrentSkipListSet弱一致性迭代器的特性:它基于集合的快照视图创建,迭代过程中不会反映迭代开始后的新增元素,但会反映已存在元素的删除/修改;而且它绝对不会导致元素“从未出现在集合中”。

你提到线程1新增的AIRefitTask从未出现在集合里,这更可能是因为你的Comparator逻辑有问题:
ConcurrentSkipListSet判断元素是否重复,完全依赖你传入的Comparator返回0,而不是equals()方法。你的Comparator只比较executionTime:

Comparator<TimedTask> comparator = (task1, task2) -> task1.getExecutionTime().compareTo(task2.getExecutionTime());

如果有两个任务的executionTime完全相同,第二个任务调用add()时会被判定为重复元素,直接添加失败(返回false),自然不会出现在集合里。这才是你遇到的核心问题!

当然,如果元素确实被成功添加了,但迭代器遍历不到,那才是弱一致性的锅——但这种情况下元素是存在于集合中的,只是当前迭代器的快照看不到,下次遍历会处理。

二、iterator.next()与remove()会操作不同元素吗?

不会。ConcurrentSkipListSet的迭代器是线程安全的,remove()方法只会移除刚刚通过next()获取的元素。它的内部实现会在next()时定位到具体的节点,remove()会原子性地删除该节点,不会出现“操作不同元素”的情况。

三、可行解决方案

1. 修复Comparator逻辑,避免重复元素判定错误

必须给相同executionTime的任务增加唯一区分逻辑,比如给TimedTask添加一个唯一ID(比如UUID),修改Comparator:

Comparator<TimedTask> comparator = (task1, task2) -> {
    // 先比较执行时间
    int timeCompare = task1.getExecutionTime().compareTo(task2.getExecutionTime());
    if (timeCompare != 0) {
        return timeCompare;
    }
    // 执行时间相同时,用唯一ID区分
    return task1.getTaskId().compareTo(task2.getTaskId());
};

如果暂时不想加ID,也可以用System.identityHashCode()(虽然有极小的碰撞概率,但大部分场景够用):

return Integer.compare(System.identityHashCode(task1), System.identityHashCode(task2));

同时,建议在add()方法里打印添加结果,验证是否真的添加成功:

public void add(TimedTask task) {
    log.trace("Add task {}", task);
    boolean added = sortedEvents.add(task);
    if (!added) {
        log.warn("Failed to add task {} - duplicate execution time with existing task?", task);
    }
}

2. 解决弱一致性迭代器导致的新增任务无法及时处理问题

如果需要在一次handleClockTick中处理所有过期任务(包括遍历过程中新增的),可以放弃迭代器遍历,改用循环获取并移除第一个元素的方式:

public void handleClockTick(ClockTick event) {
    LocalDateTime now = date.getCurrentDate();
    logContent("Task list BEFORE daily processing ("+now+")");
    
    // 循环处理直到没有过期任务
    while (true) {
        TimedTask task = sortedEvents.first();
        // 集合为空或第一个任务未过期,退出循环
        if (task == null || task.getExecutionTime().isAfter(now)) {
            break;
        }
        // 原子性移除并处理任务
        if (sortedEvents.remove(task)) {
            try {
                log.trace("BEFORE: Execute task {} scheduled for {} on {}", task, task.getExecutionTime(), now);
                task.run();
                log.trace("AFTER: Execute task {} scheduled for {} on {}", task, task.getExecutionTime(), now);
            } catch (Exception e) {
                log.error("Failed to execute timed task", e);
            }
        }
    }
    
    logContent("Task list AFTER daily processing");
}

这种方式每次都从集合头部取最新的第一个元素,能处理遍历过程中新增的过期任务。

3. 遍历集合快照处理

如果接受“本次处理只处理调用时刻的过期任务,新增任务下次处理”,可以创建集合的完整快照再遍历:

public void handleClockTick(ClockTick event) {
    LocalDateTime now = date.getCurrentDate();
    logContent("Task list BEFORE daily processing ("+now+")");
    
    // 创建集合的快照副本
    List<TimedTask> taskSnapshot = new ArrayList<>(sortedEvents);
    for (TimedTask task : taskSnapshot) {
        if (task.getExecutionTime().isBefore(now)) {
            // 尝试从原集合移除并处理
            if (sortedEvents.remove(task)) {
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("Failed to execute timed task", e);
                }
            }
        } else {
            // 集合有序,后面的任务都未过期,直接跳出
            break;
        }
    }
    
    logContent("Task list AFTER daily processing");
}

这里的new ArrayList<>(sortedEvents)会创建一个当前时刻的完整快照,能看到所有已添加的元素,避免弱一致性迭代器的限制。


内容的提问来源于stack exchange,提问作者hotzst

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:12:49