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

HashMap定时清理方法触发ConcurrentModificationException求助

解决HashMap缓存清理时的ConcurrentModificationException问题

嘿,我来帮你排查这个问题!你遇到的ConcurrentModificationException其实和你想的不一样——哪怕你的清理方法同一时间只执行一次,只要有其他线程在修改这个HashMap的结构(比如添加缓存时的put操作),就会触发这个异常。

为什么会报错?

HashMap的迭代器是fail-fast的:当迭代器创建后(比如你调用stream().min()时,内部会生成迭代器遍历entrySet),如果HashMap的结构发生了修改(比如put、remove、扩容),迭代器就会立刻抛出这个异常,不管修改操作是来自当前线程还是其他线程。

你的情况很可能是:清理方法在执行stream().min()遍历缓存时,另一个线程正在往cache里添加新的缓存文件(调用put),这就触发了迭代器的fail-fast机制,导致报错。

解决方案

这里有两种靠谱的解决思路,你可以根据自己的场景选择:

1. 换成线程安全的ConcurrentHashMap

ConcurrentHashMap的迭代器是弱一致的,它不会因为并发的结构修改而抛出异常,而是会遍历修改前的快照(或者尽可能新的状态),完美适配多线程读写的场景。

只需要把缓存的声明改成:

private ConcurrentHashMap<String, CachedFile> cache = new ConcurrentHashMap<>();

这种方式不需要额外加锁,代码改动最小。

2. 用同步块保证操作的原子性

如果你不想替换HashMap,可以给所有涉及cache的操作加锁,保证同一时间只有一个线程能修改或遍历它:

修改你的清理方法,把整个逻辑包裹在synchronized块里:

public void deleteFileCache() {
    synchronized (cache) {
        // 删除12小时未访问的文件
        cache.entrySet().stream()
            .filter(entry -> LocalDateTime.now().minus(12, ChronoUnit.HOURS).isAfter(entry.getValue().getLastAccessed()))
            .forEach(entry -> {
                File file = new File(tempFolder, entry.getKey());
                file.delete();
            });
        // 移除HashMap中对应的条目
        cache.entrySet().removeIf(entry -> LocalDateTime.now().minus(12, ChronoUnit.HOURS).isAfter(entry.getValue().getLastAccessed()));
        
        // 剩余空间不足时删除最旧的文件
        long freeSpace = tempFolder.getFreeSpace();
        while (freeSpace < 6000000000L) {
            Optional<String> fileToDelete = cache.entrySet().stream()
                .min(Comparator.comparing(entry -> entry.getValue().getLastAccessed()))
                .map(Map.Entry::getKey);
            
            if (fileToDelete.isEmpty()) {
                break; // 没有缓存文件可删了,退出循环
            }
            
            String filename = fileToDelete.get();
            new File(tempFolder, filename).delete();
            cache.remove(filename);
            freeSpace = tempFolder.getFreeSpace();
        }
    }
}

同时,所有其他修改cache的地方(比如添加缓存的put方法)也要用同一个锁对象同步:

public void addCachedFile(String key, CachedFile file) {
    synchronized (cache) {
        cache.put(key, file);
    }
}

这样就能确保遍历和修改操作不会同时发生,彻底解决并发修改异常的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:15:08