在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)是更好的选择,原因有两个:
- 因为在您的场景中,读取更频繁,所以应该减少它们的开销。 在这种情况下,添加其他同步(例如
volatile
)会对您不利。 - 通过使用带有其他自定义安全措施(可能有错误)的可变对象,您几乎可以通过使用
ConcurrentHashMap
来ConcurrentHashMap
使您的生活更轻松的程度。
只是一个想法。 您声明写入速率很低,但是为了参数,我们假设changeData
方法有多个并发写入/调用。 然后,调用方法的线程最后可能首先完成(在两种方法中)。
如果您的应用程序逻辑假定将遵循插入顺序,则可能会产生错误的结果。 在这种情况下,方法changeData
的主体是您的关键部分 ,每个定义意味着它不应该同时执行。
关键部分定义对应用程序域语义和代码结构高度敏感,因此我无法确定该方法是否应被视为关键部分。 通过变量的名称进行猜测,并假设您的地图是来自数据库的用户数据缓存,我猜你可以忽略这个答案。 但要仔细想想它,但:)
如果所有的写操作都通过这个方法,那么这将是代码的草图(你可以使用非线程安全的地图实现):
public void changeData(UUID oldId, Foo newInfo) { synchronized(SomeClass.class) { // global lock //update logic } }
这只是一个草图来说明当然。 如果这是问题,你最有可能使用一些Java并发结构。
如果您可以选择创建不可变类,那么实现#A
会更好:就地修改实现和维护起来要困难得多。
有时去不可变路线可能不是一种选择,因为需要频繁修改相对较大的对象。 在这种情况下,您可能需要重新考虑并发哈希映射到您的设计的应用程序,因为它是同步的事实并没有给您太多的优势。