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

时间:2013-08-20 16:19:15

标签: java concurrency thread-safety concurrenthashmap

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

ConcurrentMap<UUID, Data> map = new ConcurrentHashMap<UUID, Data>();

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

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字段,原子引用或最终并发字段使Data类对象可变但是线程安全,并根据需要简单地修改对象:

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

C)以上都不是

3 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:3)

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

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

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

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

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

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

答案 2 :(得分:2)

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

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