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

Java 如何运行定期清理列表中某些元素的后台线程?,java,Java,我目前正在实现缓存。我已经完成了基本的实现,如下所示。我想做的是运行一个线程,该线程将删除满足某些条件的条目 class Cache { int timeLimit = 10; //how long each entry needs to be kept after accessed(marked) int maxEntries = 10; //maximum number of Entries HashSet<String> set = new HashSe

我目前正在实现缓存。我已经完成了基本的实现,如下所示。我想做的是运行一个线程,该线程将删除满足某些条件的条目

class Cache {
    int timeLimit = 10; //how long each entry needs to be kept after accessed(marked)
    int maxEntries = 10; //maximum number of Entries
    HashSet<String> set = new HashSet<String>();   
    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);
    }
    ....
}
类缓存{
int timeLimit=10;//访问(标记)后每个条目需要保留多长时间
int maxEntries=10;//最大条目数
HashSet=newhashset();
公共无效添加(条目t){
....
}
公共入口访问(字符串键){
//标记已使用的条目
//由于它已被标记,后台线程应在timeLimit秒后删除此条目。
返回set.get(键);
}
....
}
我的问题是,我应该如何实现后台线程,以便线程绕过集合中的条目并删除已标记的条目&&(上次访问时间-现在)>timeLimit

编辑


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

首先,如下面的注释所示,访问您的收藏
synchronized
或使用
ConcurrentHashSet
a
ConcurrentHashMap
based
Set


其次,编写新线程,并将其实现为一个无休止的循环,定期迭代先前的集合并删除元素。编写此类时,应该使用构造函数中正确的集合对其进行初始化,这样就不必担心“如何访问正确的集合”。

为什么要重新发明轮子?(以及任何像样的缓存实现)都可以为您做到这一点。此外,更轻量级的功能还可以自动删除旧条目

如果你真的想自己实现这一点,其实并不是那么简单

  • 记住同步。您应该使用
    ConcurrentHashMap
    synchronized
    关键字来存储条目。这可能真的很棘手

  • 您必须以某种方式存储每个条目的上次访问时间。每次访问条目时,都必须更新该时间戳

  • 想想驱逐政策。如果缓存中有多个
    maxEntries
    ,那么首先删除哪些条目

  • 你真的需要背景线程吗

    这是令人惊讶的,但EhCache(enterprise ready and proven)并没有使用后台线程使旧条目无效。相反,它会等待映射已满,然后缓慢地删除条目。这看起来是一个很好的折衷方案,因为线程很昂贵

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

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

    我个人会使用的类型。它已经是线程安全的,并且内置了基于某个时间限制从缓存中逐出的方法。如果希望线程定期扫描它,可以执行以下操作:

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

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


    顺便说一句:如果您使用LinkedHashMap,您可以通过覆盖RemoveedStantry(参见其javadocs示例)将其用作LRU缓存。

    首先,您提供的代码是不完整的,因为
    哈希集上没有
    get(key)
    (因此我假设您指的是某种
    Map
    ),并且您的代码没有提到任何内容“标记。”还有很多方法可以进行缓存,在不知道要缓存什么以及为什么要缓存的情况下,很难选择最佳解决方案

    在实现缓存时,通常假设数据结构将由多个线程并发访问。因此,您需要做的第一件事是使用线程安全的备份数据结构。
    HashMap
    不是线程安全的,但是
    ConcurrentHashMap
    是线程安全的。还有许多其他的缓冲区rrent
    Map
    实现,即,和。除了Map之外,还有其他方法来构建缓存,它们的有用性取决于您的用例。不管怎样,您很可能需要使备份数据结构线程安全,即使您决定不需要后台线程,而是在一次更新时逐出过期的对象尝试从缓存中检索条目。或者让GC使用
    SoftReference
    s删除条目

    一旦使缓存线程的内部安全,就可以启动一个新的(很可能是守护)线程,定期扫描/迭代缓存并删除旧条目。该线程将在循环中执行此操作(直到中断,如果希望再次停止),然后在每次扫描后休眠一段时间

    但是,你应该考虑是否值得你自己构建缓存实现。编写线程安全代码并不容易,我建议你在尝试编写自己的缓存实现之前先研究一下它。我可以在实践中推荐java并发。 当然,更简单的方法是使用现有的缓存实现

    • 这两种缓存都是通用的,可以满足典型“企业”应用程序中的大多数缓存需求
    • 是一个为分布式使用而优化的缓存,因此可以缓存比一台机器上可以容纳的数据更多的数据。我也喜欢它的
      ConcurrentMa