使用WeakHashMap模拟DELETE级联操作

9
我正在开发一个监控计算机的服务。可以通过Web GUI添加或删除要监视的计算机。我基本上将报告的数据存储在各种映射中,例如Map<Computer, Temperature>。现在,随着收集到的数据越来越多,并且数据结构变得更加复杂(包括计算机相互引用),我需要一个概念来处理从监视中删除计算机时会发生什么。基本上,我需要删除被删除计算机报告的所有数据。最像KISS原则的方法是手动从内存中删除数据,如:
public void onRemove(Computer computer) {
    temperatures.remove(computer);
    // ...
}

每当我添加功能时,就必须更改这种方法 :-( 我知道Java有一个WeakHashMap,因此我可以像这样存储报告的数据:

 Map<Computer, Temperature> temperatures = new WeakHashMap<>();

每当计算机从监控中移除时,您可以调用System.gc()以便从这些映射中及时删除所有相关数据。

第一种方法似乎有点像原始的MyISAM表,而第二种方法则类似于InnoDB表中的级联删除。但仍然感觉有点不舒服,可能是错误的方法。您能指出使用WeakHashMaps的优缺点或提出其他解决方案吗?


你能展示一下为什么以及如何在添加一个功能时改变了 onRemove 方法吗?这将有助于澄清你实际的问题是什么... - assylias
@assylias 好吧,我需要从所有地图中删除数据(温度、邻居、操作系统、本地用户等)。实际上,我有更多的实体比 Computer 产生数据,一旦用户将它们从监视列表中移除,就必须将它们删除。 - steffen
你的想法有几个问题,这些问题在多个答案中都有提到,但还有一个问题。System.gc()不能保证做任何事情。请参阅此错误报告http://bugs.java.com/view_bug.do?bug_id=6668279。我使用WeakHashMap来存储可能被删除的对象的元数据,以便我需要更少的维护这些映射,但是从WHM中未删除对象并不意味着任何事情。您永远无法知道GC是否已尝试收集(销毁)该对象。 - aalku
5个回答

5

不确定这对你的情况是否可行,但是你的计算机类可以拥有所有属性,然后有一个monitoredComputers列表(或者有一个名为MonitoredComputers的包装器类,可以包装任何需要的逻辑,例如getTemperatures())。这样它们就可以从列表中删除,而不必查找所有属性列表。如果计算机被另一台计算机引用,则必须遍历该列表并从具有引用的计算机中删除引用。


这确实是另一种解决方案:要么将计算机相关数据存储在计算机对象本身中,要么将其存储在某个“ComputerReport”对象中,该对象仅通过(受监视的)计算机对象才能被_强引用_。 - steffen
是的,确切地说。当你意识到你对数据的另一种交叉部分感兴趣时,重新构建模型可能会更容易。 - Viktor Mellgren
什么是交叉部分? - steffen
交叉部分可能是错误的词,我的意思是要对数据进行分组,什么是主要数据。你是在处理温度还是在处理计算机。类比:你应该将一个人映射到几个乐队,还是将一个乐队映射到几个人。也就是说,这取决于你如何处理数据以及你想呈现什么类型的操作/视图。 - Viktor Mellgren

2
为什么不使用实际的SQL数据库呢?你可以使用嵌入式数据库引擎,如H2Apache Derby / Java DBHSQLDBSQLite。使用嵌入式数据库引擎还有以下优点:
  1. 您可以使用相应的数据库引擎命令行客户端随时检查监控数据的实时内容。
  2. 您可以连接到共享数据库实例,构建新的访问和操作数据的工具。
  3. 模式本身是关于监控数据结构和实体之间关系的一种形式的文档。
  4. 通过模式规范化,您可以为不同类型的计算机存储不同类型的数据。
  5. 您可以备份监控数据。
  6. 如果您需要重新启动监控服务器,您不会丢失所有监控数据。
您的Web UI可以使用JPA实现,如Hibernate来访问监控数据并添加新记录。或者,为了更轻量级的解决方案,您可以考虑使用Spring Framework的JdbcTemplateSimpleJdbcInsert类。还有OrmLiteActiveJDBCjOOQ,它们各自旨在比JDBC提供更简单的数据库访问。

因为计算机经常被访问(每分钟多次),GUI将通过WebSockets访问它们以及其他实体,因此每台计算机显示的温度将在报告后立即更新。我认为,使用内存数据库并不容易实现这一点。 - steffen
@steffen:哦,所以你没有历史数据,只有最新的读数? - Daniel Trebbien
主要只存储当前数据!我会把历史数据存储在其他地方,因为那会消耗太多内存。 - steffen

2

我不确定使用WeakHashMap是否是一个好主意。正如你所说,你可能会从多个地方引用Computer对象,因此需要确保除了其中一个之外的所有引用都通过弱引用,并在删除计算机时删除强引用。由于你无法控制弱引用何时被删除,因此可能无法得到一致的结果。

如果你不想手动维护删除,可以在Computer对象上设置一个标志,比如isAlive()。然后将计算机存储在特殊的Map和Collection子类中,在读取时检查计算机是否存活,如果不是,则静默删除它。例如,在Map<Computer,?>上,get方法将检查计算机是否存活,如果不是,则删除它并返回null。

或者Map和Collection的子类可以只向单个computerRemoved()事件注册自己,自动知道如何删除已删除的计算机,而无需手动编写删除代码。只需确保在特殊的map和collection内部仅保留对Computer的引用即可。


由于您无法控制何时删除弱引用,因此可能无法获得一致的结果。移除计算机并不经常发生,System.gc()在这种情况下是可以接受的。然而,将Temperatures封装在某个特殊对象中,并监听computerRemoved事件可能是一个好主意。 - steffen

1
WeakHashMap存在的问题在于管理对Computer对象的引用似乎很困难且容易破坏。
基于哈希表的Map接口实现,使用弱键。当一个WeakHashMap中的键不再被普通使用时,它的条目将自动被删除。更准确地说,给定键的映射的存在不会防止该键被垃圾回收器丢弃,即被设置为finalizable、finalized,然后被回收。当一个键已经被丢弃时,它的条目就从地图上有效地移除了,因此这个类的行为与其他Map实现有所不同。
可能存在对Computer对象的引用仍然存在于某些地方,而WeakHashMaps不会删除对象。我更喜欢一种更确定的方法。
但如果你决定走这条路,你可以通过将所有这些Computer对象键包装在一个具有严格控制的类中来缓解我指出的问题。这个包装对象将创建和存储键,并注意永远不让这些键的引用泄漏出去。

我认为,如果WeakHashMap中还有另一个对计算机的强引用,并且它不会被移除,那么它与只有强引用时一样确定性。相反,如果忘记删除对计算机的强引用,则会立即在WeakHashMap中显示条目,从而产生错误。 - steffen
如果有人发出删除计算机的请求,但如果内存中仍然存在对计算机的引用,那么实际上是无法删除的。因此,重要的是要管理好这个问题。 - Jose Martinez
如果计算机的引用仍然存在,则可能会出现内存泄漏。最好立即失败,而不是在某个随机时间点失败。特别是如果它是一个全天候运行的服务。 - steffen
说“没关系,如果它不起作用,我们会有其他问题,最终可能会注意到”似乎不太对。事实上,您期望将所有计算机对象都保存在内存中,这意味着每个计算机对象都将在内存中,然后对其进行第二次引用不会出现故障快速内存泄漏,因为引用不占用大量内存,而对象本身则不同。无论如何,我并不是说不要这样做,我是说要注意通过确保不会意外保留引用来避免这种潜在问题。 - Jose Martinez
这是一个很好的测试。当发出删除命令时,开始请求对象并观察它消失需要多长时间。您还需要考虑您使用的JVM,因为结果可能会因所使用的JVM而异。 - Jose Martinez
我觉得你误解了我的意思。当一个强引用在删除后仍然存在时,可能会导致内存泄漏:强引用会阻止JVM释放对象内存(不仅是引用本身)。WeakHashMap可以轻松地告诉你是否错过了引用:如果WeakHashMap在删除后仍包含该对象,则表明该对象仍然具有强引用。 - steffen

0

我是一名初学者,也许这个方法有些笨拙:

为什么不将被监控的计算机存储在HashMap中,而已经移除的计算机则存储在WeakHashMap中呢?这样所有已移除的计算机都是独立的,易于处理,并且由垃圾回收器清理最早的条目。


这个并没有解决问题。而且我不明白为什么你想把计算机放在WeakHashMap中,只有当所有强引用都被置空后,它们才会被垃圾回收。 - steffen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接