首页 > 解决方案 > ConcurrentHashMap:按条件删除

问题描述

我有一个ConcurrentHashMap用作内存存储(或缓存,你可能会说)

我想要实现的是:同时检查一个项目是否“准备好”,如果是,则将其从地图中删除(+将其返回给调用者)。没有直接的方法可以让我做到这一点。

我想出的唯一解决方案是ItemContainer包含项目和元数据(isReady字段)。在每次访问时,我都必须申请mergecompute操作。基本上在每次访问/检查时替换对象的容器。

问题:

  1. 我的解决方案看起来合理吗?
  2. 有没有什么好的库可以实现类似的东西?

我按要求添加了“样板”代码:

public class Main {

    public static void main(String[] args) {
        Storage storage = new Storage();
        storage.put("1", new Record("s", 100));
        storage.put("2", new Record("s", 4));
        storage.removeIf("1", Main::isReady);
    }

    public static boolean isReady(Record record) {
        return record.i > 42;
    }

    public static class Record {

        public Record(String s, Integer i) {
            this.s = s;
            this.i = i;
        }

        String s;
        Integer i;
    }

    public static class Storage {
        ConcurrentHashMap<String, Record> storage = new ConcurrentHashMap<>();

        public void put(String key, Record record) {
            storage.put(key, record);
        }

        public Record removeIf(String key, Function<Record, Boolean> condition) {
            return null; // TODO: implement
        }
    }
}

其他解决方案(权衡):

  1. 始终remove()检查,然后merge()返回地图。
  2. 使用具有一些合理的项目疏散策略(即 LRU)的缓存并仅检查已疏散的项目。

基于@ernest_k 解决方案:

public Record removeIf(String key, Predicate<Record> condition) {
    AtomicReference<Record> existing = new AtomicReference<>();

    this.storage.computeIfPresent(key, (k, v) -> {
        boolean conditionSatisfied = condition.test(v);

        if (conditionSatisfied) {
            existing.set(v);
            return null;
        } else {
            existing.set(null);
            return v;
        }
    });

    return existing.get();
}

标签: javamultithreadingcachingdata-structuresthread-safety

解决方案


ConcurrentHashMap已经为您提供了原子性的保证computeIfPresent

如果指定键的值存在,则尝试在给定键及其当前映射值的情况下计算新映射。整个方法调用以原子方式执行。在计算过程中,其他线程对该映射的某些尝试更新操作可能会被阻塞,因此计算应该简短而简单,并且不得尝试更新该映射的任何其他映射。

所以你可以使用它:

public Record removeIf(String key, Predicate<Record> condition) {

    AtomicReference<Record> existing = new AtomicReference<>();

    this.storage.computeIfPresent(key, (k, v) -> {
        existing.set(v);
        return condition.test(v) ? null : v;
    });

    return existing.get();
}

请注意,我使用Predicate<Record>它应该优先于Function<Record, Boolean>.

将当前值存储在AtomicReference此处的原因是确保返回的值与测试谓词的值相同(否则可能存在竞争条件)。


推荐阅读