如何运行后台线程来定期清理列表中的某些元素?

我目前正在实施缓存。 我已完成基本实现,如下所示。 我想要做的是运行一个线程,删除满足特定条件的条目。

class Cache { int timeLimit = 10; //how long each entry needs to be kept after accessed(marked) int maxEntries = 10; //maximum number of Entries HashSet set = new HashSet(); public void add(Entry t){ .... } public Entry access(String key){ //mark Entry that it has been used //Since it has been marked, background thread should remove this entry after timeLimit seconds. return set.get(key); } .... } 

我的问题是,我应该如何实现后台线程,以便线程绕过集合中的条目并删除已marked && (last access time - now)>timeLimit

编辑

上面只是代码的简化版本,我没有写同步语句。

你为什么重新发明轮子? EhCache (以及任何体面的缓存实现)都会为您完成此任务。 也更轻巧 MapMaker 来自Guava的 Cache可以自动删除旧条目。

如果你真的想自己实现它,那就不是那么简单了。

  1. 记住同步。 您应该使用ConcurrentHashMapsynchronized关键字来存储条目。 这可能非常棘手。

  2. 你必须以某种方式存储每个条目的最后访问时间。 每次访问条目时,都必须更新该时间戳。

  3. 想想驱逐政策。 如果缓存中有多个maxEntries ,首先要删除哪些?

  4. 你真的需要一个后台线程吗?

    这是令人惊讶的,但EhCache(企业就绪且经过validation)不使用后台线程来使旧条目无效)。 相反,它等待地图填满并懒惰地删除条目。 这看起来是一个很好的权衡,因为线程很昂贵。

  5. 如果你有一个后台线程,那么每个缓存还是一个全局? 您是在创建新缓存时启动新线程还是拥有所有缓存的全局列表? 这比你想象的要难……

一旦你回答了所有这些问题,实现就相当简单了:每隔一秒左右查看所有条目,如果满足你已编写的条件,则删除条目。

我亲自使用Guava的Cache类型。 它已经是线程安全的,并且内置了一些方法,可以根据时间限制从缓存中逐出。 如果你想要一个线程定期扫描它,你可以这样做:

  new Thread(new Runnable() { public void run() { cache.cleanUp(); try { Thread.sleep(MY_SLEEP_DURATION); } catch (Exception e) {}; } }).start(); 

我不认为你真的需要一个后台线程。 相反,您可以在执行查找之前或之后删除过期的条目。 这简化了整个实现,很难区分。

顺便说一句:如果您使用LinkedHashMap,可以通过覆盖removeEldestEntry将其用作LRU缓存(有关示例,请参阅其javadoc)

首先,你呈现的代码是不完整的,因为HashSet上没有get(key) (所以我假设你的意思是某种Map )而你的代码没有提到任何“标记”。 还有很多方法可以进行缓存,如果不知道要缓存的内容以及原因,很难找出最佳解决方案。

在实现缓存时,通常假设数据结构将由多个线程同时访问。 因此,您需要做的第一件事是使用线程安全的后备数据结构。 HashMap不是线程安全的,但是ConcurrentHashMap是。 还有许多其他并发Map实现,即Guava , Javolution和高级lib 。 除了地图之外,还有其他方法可以构建缓存,它们的用处取决于您的用例。 无论如何,即使您决定不需要后台线程,而且在尝试从缓存中检索过期对象时,也很可能需要使后备数据结构成为线程安全的。 或者让GC使用SoftReference删除条目。

一旦你的缓存线程安全的内部,你可以简单地启动一个新的(很可能是daemonized)线程,定期扫描/迭代缓存并删除旧条目。 线程将在循环中执行此操作(直到被中断,如果您希望能够再次停止它),然后在每次扫描后hibernate一段时间。

但是,您应该考虑是否值得为您构建自己的缓存实现。 编写线程安全的代码并不容易,我建议您在努力编写自己的缓存实现之前先研究它。 我可以推荐Java Concurrency in Practice一书。

当然,更简单的方法是使用现有的缓存实现。 Java-land中有许多选项,都有自己独特的权衡取舍。

  • EhCache和JCS都是通用缓存,适合大多数缓存需求,可以在典型的“企业”应用程序中找到。
  • Infinispan是一种针对分布式使用进行了优化的缓存,因此可以缓存比单个机器上的数据更多的数据。 我也喜欢它的基于ConcurrentMap的API。
  • 正如其他人所提到的,Googles Guava库有一个Cache API,对于小型内存缓存非常有用。

由于您希望限制缓存中的条目数,因此您可能对对象池而不是缓存感兴趣。

  • Apache Commons-Pool被广泛使用,其API类似于您自己构建的API。
  • 另一方面, Stormpot有一个相当不同的API,我几乎只提到它,因为我写了它。 它可能不是你想要的,但谁能确定你不知道你想要缓存什么,为什么?

首先,要synchronized或使用对集合的访问权限 ConcurrentHashSet 基于ConcurrentHashMapSet如下面的注释所示。

其次,编写新线程,并将其实现为无限循环,定期迭代先前的集合并删除元素。 您应该以在构造函数中使用正确的集合初始化它的方式编写此类,这样您就不必担心“如何访问正确的集合”。