重新插入番石榴清除过滤器的条目是否安全?

2022-09-04 04:30:51

我有一个番石榴(或者更确切地说,我正在从 迁移到 ),这些值表示长时间运行的作业。我想向缓存中添加行为,因为这是清理它的最佳方法;但是,即使一段时间没有通过缓存访问作业,作业可能仍在运行,在这种情况下,我需要防止将其从缓存中删除。我有三个问题:CacheMapMakerCacheexpireAfterAccess

  1. 重新插入在回调期间被删除的缓存条目是否安全?RemovalListener

  2. 如果是这样,它是否是线程安全的,以至于当回调仍在另一个线程中发生时,就不可能为该键生成第二个值?CacheLoaderRemovalListener

  3. 有没有更好的方法来实现我想要的?这不是严格/只是一个“缓存” - 为每个键使用一个且只有一个值至关重要 - 但我也想在它所代表的作业完成后将条目缓存一段时间。我以前使用过,我需要的行为现在在该类中被弃用。在作业运行时定期 ping 地图是不优雅的,在我的情况下,也是不可行的。也许正确的解决方案是有两个地图,一个没有驱逐,另一个有,并在完成时迁移它们。MapMaker

我也会提出一个功能请求 - 这将解决问题:允许锁定单个条目以防止驱逐(然后随后解锁)。

[编辑以添加一些详细信息]:此映射中的键是指数据文件。这些值可以是正在运行的写入作业、已完成的写入作业,或者(如果没有作业正在运行)具有从文件中读取的信息的只读查找时生成的对象。重要的是,每个文件正好有零个或一个条目。我可以对这两件事使用单独的地图,但必须在每个密钥的基础上进行协调,以确保一次只存在一个或另一个。使用单个映射可以使其更简单,从而获得正确的并发性。


答案 1

我不完全清楚确切的问题,但另一种解决方案是使用缓存而不是最大大小或到期时间。每次访问缓存值(在您的示例中,开始计算)时,都应使用对此值的强引用在其他位置维护状态。这将阻止该值被 GCed。每当此值的使用率降至零时(在您的示例中,计算结束,并且该值消失的OK),您可以删除所有强引用。例如,您可以使用带有 Cache 值的 AtomicLongMap 作为 AtomicLongMap 键,并定期调用该映射。softValues()removeAllZeros()

请注意,正如 Javadoc 所述,使用 确实需要权衡利弊。softValues()


答案 2

我查看了Guava代码,发现(用作底层实现)将删除通知添加到内部队列中,并在以后处理它们。因此,从删除回调中将条目重新插入到映射中在技术上是“安全的”,但会打开一个窗口,在此期间该条目不在映射中,因此另一个线程可以通过 .CustomConcurrentHashMapCacheBuilderCacheLoader

我在上面的评论中使用@Louis的建议解决了这个问题。主映射根本没有过期时间,但每次我在该映射中查找某些内容时,我还会向具有 .在该辅助缓存的删除侦听器中,我对是否从主映射中删除条目做出了一些决定。没有其他任何内容使用辅助缓存。这似乎是有条件驱逐的一个优雅的解决方案,并且正在起作用。(我真的仍在使用番石榴r09 MapMaker作为这个解决方案,但它应该同样适用于番石榴11。expireAfterAccess