首页 > 解决方案 > ConcurrentHashMap 移除键

问题描述

我试图弄清楚如何编写一个线程安全的、过期的条目、缓存。这个缓存会用作no-hits缓存,这样如果某个存储在某个存储中没有找到条目,我会将它放入这个缓存中,避免接下来几分钟的后续调用。

将有多个线程读取和写入此缓存。

我的应用程序中只有一个 ThreadSafeCache 实例。

我不确定删除 contains 方法中的条目是否会出现同步问题。

我如何测试这个类的线程安全性?

亲切的问候

public class ThreadSafeCache 
{
    private final Clock clock = Clock.systemUTC();
    private final Duration expiration = Duration.ofMinutes(10);
    private final ConcurrentHashMap<CacheKey, CacheValue> internalMap = new ConcurrentHashMap<>();

    public boolean contains(String a, String b, byte[] c, String d)
    {
        CacheKey key = new CacheKey(a, b, c, d);
        CacheValue value = internalMap.get(key);

        if (value == null || value.isExpired())
        {
            internalMap.remove(key);
            return false;
        }

        return true;
    }

    public void put(String a, String b, byte[] c, String d)
    {
        internalMap.computeIfAbsent(new CacheKey(a, b, c, d), key -> new CacheValue());
    }


    private class CacheValue
    {
        private final Instant insertionDate;

        private CacheValue()
        {
            this.insertionDate = clock.instant();
        }

        boolean isExpired()
        {
            return Duration.between(insertionDate, 
 clock.instant()).compareTo(expiration) > 0;
        }
    }
}

标签: javaconcurrency

解决方案


您在同一个函数中调用 2 个 Map 操作,这意味着存在交错的范围(即另一个操作发生在函数的 2 个操作之间,从而改变了它的行为)。要解决此问题,您可以将地图操作放在一个synchronized (internalMap) {}块中。请注意,您必须对在 2 个离散方法调用中与地图交互的任何方法执行此操作。

从代码风格的角度来看,在contains方法中修改映射是不好的做法。这将使您的代码难以预测。第一次访问您的代码的其他人(或几个月后的您)可能不记得contains()实际修改了缓存。contains意味着它只是检查缓存,而不是修改它。

我的建议是:

  • 如果密钥已过期,只需返回 false。
  • 在该get()方法中,检查该值是否已过期,如果已过期,则在那里计算一个新值。

推荐阅读