Java 从Guava RemovalListener重新插入条目是否安全?
我有一个番石榴Java 从Guava RemovalListener重新插入条目是否安全?,java,caching,concurrency,guava,Java,Caching,Concurrency,Guava,我有一个番石榴缓存(或者更确切地说,我正在从MapMaker迁移到Cache),这些值表示长期运行的作业。我想在缓存中添加expireAfterAccess行为,因为这是清理缓存的最佳方式;但是,作业可能仍在运行,即使它在一段时间内没有通过缓存访问,在这种情况下,我需要防止它从缓存中删除。我有三个问题: 在RemovalListener回调期间重新插入正在删除的缓存项是否安全 如果是这样的话,它是线程安全的吗?CacheLoader不可能在另一个线程中仍在进行RemovalListener回调时
缓存
(或者更确切地说,我正在从MapMaker
迁移到Cache
),这些值表示长期运行的作业。我想在缓存中添加expireAfterAccess
行为,因为这是清理缓存的最佳方式;但是,作业可能仍在运行,即使它在一段时间内没有通过缓存访问,在这种情况下,我需要防止它从缓存中删除。我有三个问题:
RemovalListener
回调期间重新插入正在删除的缓存项是否安全CacheLoader
不可能在另一个线程中仍在进行RemovalListener
回调时为该键生成第二个值MapMaker
,现在我需要的行为在该类中已被弃用。在作业运行时定期ping地图是不雅观的,在我的情况下是不可行的。也许正确的解决方案是有两个地图,一个没有逐出,另一个有,并在完成时将它们迁移到另一个地图上[编辑以添加一些详细信息]:此地图中的键指的是数据文件。这些值可以是正在运行的写入作业、已完成的写入作业,或者(如果没有作业正在运行)在查找对象上生成的只读作业,其中包含从文件读取的信息。重要的是每个文件都有零个或一个条目。我可以为这两件事使用单独的地图,但必须在每个键的基础上进行协调,以确保在同一时间只有一个或另一个存在。使用单个映射可以简化并发性的正确性。我不完全清楚确切的问题,但另一个解决方案是使用带有
softValues()
的缓存,而不是最大大小或过期时间。每次访问缓存值(在您的示例中,启动计算)时,都应该在其他地方使用该值的强引用来维护状态。这将防止对该值进行GCD。每当该值的使用降至零时(在您的示例中,计算结束并且该值可以消失),您可以删除所有强引用。例如,您可以使用带有缓存值的作为AtomicLongMap键,并定期在映射上调用removeAllZeros()
请注意,正如Javadoc所述,
softValues()
的使用确实带来了折衷。我查看了Guava代码,发现CustomConcurrentHashMap
(该CacheBuilder
用作底层实现)将删除通知添加到内部队列,并在以后处理它们。因此,从移除回调将条目重新插入映射在技术上是“安全的”,但会打开一个窗口,在此期间该条目不在映射中,因此另一个线程可以通过CacheLoader
生成备用条目
我在上面的评论中使用@Louis的建议解决了这个问题。主映射根本没有过期,但每次我在该映射中查找某个内容时,我也会将一个条目添加到辅助缓存中,该缓存具有
expireAfterAccess
。在该辅助缓存的删除侦听器中,我决定是否从主映射中删除条目。没有其他东西使用辅助缓存。这似乎是一个优雅的有条件驱逐的解决方案,它的工作。(我仍然在使用Guava r09 MapMaker进行此解决方案,但它对Guava 11应该同样有效。)昨天升级到Guava 11时,我想到有一种方法可以将条目锁定到缓存中:使用maximumWeight
作为唯一的删除方法,并让您的称重器为应该锁定在缓存中的条目返回0权重。我还没有通过测试来验证这一点,但是各州的文件
“当条目的权重为零时,将不考虑基于大小的逐出(尽管仍可以通过其他方式逐出)。”
为了直接解决文章中的前两个问题,从缓存中删除元素后,删除侦听器会在任意时间点触发,因此不能使用删除侦听器自动重新插入值
RemovalListener
中调用不会以任何方式中断缓存。广义地说,缓存
与
RemovalListener
触发的突变与相关的删除无关。在删除完成和侦听器被通知之间,不仅可能有另一个线程访问缓存,还可能在这两个操作之间存在明显的延迟。如前所述,“删除侦听器操作在缓存维护期间同步执行”。也就是说,维护任务是小批量执行的,对于何时(或是否)实际执行,没有提供任何保证