在ConcurrentHashMap中修改值的首选方法是什么?

假设我有一个高读,低写并且需要存储应用程序数据的并发映射:

ConcurrentMap map = new ConcurrentHashMap(); 

然后,在启动期间和通过用户输入,数据将添加到地图中:

 public void createData(Data newData) { map.put(newId, newData); // etc... } 

如果我需要更改数据,我应该:

A)使Data类对象不可变,然后每次需要对Data对象进行更改时执行put操作:

 public void changeData(UUID oldId, Foo newInfo) { Data oldData = map.get(oldId); Data newData = new Data(oldData, newInfo); // Constructor for demo only map.put(newData); saveToDatabase(newData); } 

B)使用volatile字段,primefaces引用或最终并发字段使Data类对象可变但是线程安全,并根据需要简单地修改对象:

 public void changeData(UUID oldId, Foo newInfo) { Data data = map.get(id); data.changeSomething(newInfo); saveToDatabase(data); } 

C)以上都不是

A)是更好的选择,原因有两个:

  1. 因为在您的场景中,读取更频繁,所以应该减少它们的开销。 在这种情况下,添加其他同步(例如volatile )会对您不利。
  2. 通过使用带有其他自定义安全措施(可能有错误)的可变对象,您几乎可以通过使用ConcurrentHashMapConcurrentHashMap使您的生活更轻松的程度。

只是一个想法。 您声明写入速率很低,但是为了参数,我们假设changeData方法有多个并发写入/调用。 然后,调用方法的线程最后可能首先完成(在两种方法中)。

如果您的应用程序逻辑假定将遵循插入顺序,则可能会产生错误的结果。 在这种情况下,方法changeData的主体是您的关键部分 ,每个定义意味着它不应该同时执行。

关键部分定义对应用程序域语义和代码结构高度敏感,因此我无法确定该方法是否应被视为关键部分。 通过变量的名称进行猜测,并假设您的地图是来自数据库的用户数据缓存,我猜你可以忽略这个答案。 但要仔细想想它,但:)

如果所有的写操作都通过这个方法,那么这将是代码的草图(你可以使用非线程安全的地图实现):

 public void changeData(UUID oldId, Foo newInfo) { synchronized(SomeClass.class) { // global lock //update logic } } 

这只是一个草图来说明当然。 如果这问题,你最有可能使用一些Java并发结构。

如果您可以选择创建不可变类,那么实现#A会更好:就地修改实现和维护起来要困难得多。

有时去不可变路线可能不是一种选择,因为需要频繁修改相对较大的对象。 在这种情况下,您可能需要重新考虑并发哈希映射到您的设计的应用程序,因为它是同步的事实并没有给您太多的优势。