Java 何时以及如何使用ConcurrentHashMap的额外同步?

Java 何时以及如何使用ConcurrentHashMap的额外同步?,java,multithreading,concurrenthashmap,Java,Multithreading,Concurrenthashmap,在使用ConcurrentHashMap时,我需要知道何时应该在代码中添加一些同步块。假设我有一种方法,比如: private static final ConcurrentMap<String, MyObjectWrapper> myObjectsCache = new ConcurrentHashMap<>(CACHE_INITIAL_CAPACITY); public List<MyObject> aMethod(List<String&

在使用ConcurrentHashMap时,我需要知道何时应该在代码中添加一些同步块。假设我有一种方法,比如:

private static final ConcurrentMap<String, MyObjectWrapper> myObjectsCache = new ConcurrentHashMap<>(CACHE_INITIAL_CAPACITY);

    public List<MyObject> aMethod(List<String> ids, boolean b) {
        List<MyObject> result = new ArrayList<>(ids.size());
        for (String id : ids) {
            if (id == null) {
                continue;
            }
            MyObjectWrapper myObjectWrapper = myObjectsCache.get(id);
            if (myObjectWrapper == null) {
                continue;
            }
            if (myObjectWrapper.getObject() instanceof MyObjectSub) {
                ((MyObjectSub) myObjectWrapper.getObject()).clearAField();
                myObjectWrapper.getObject().setTime(System.currentTimeMillis());
            }
            result.add(myObjectWrapper.getObject());
            if (b) {
                final MyObject obj = new MyObject(myObjectWrapper.getObject());
                addObjectToDb(obj);
            }
        }
        return result;
    }
private static final ConcurrentMap myObjectsCache=新ConcurrentHashMap(缓存初始容量);
公共列表方法(列表ID,布尔b){
列表结果=新的ArrayList(ids.size());
用于(字符串id:ids){
if(id==null){
继续;
}
MyObjectWrapper MyObjectWrapper=myObjectsCache.get(id);
if(myObjectWrapper==null){
继续;
}
if(myObjectWrapper.getObject()实例为MyObjectSub){
((MyObjectSub)myObjectWrapper.getObject()).clearAField();
myObjectWrapper.getObject().setTime(System.currentTimeMillis());
}
add(myObjectWrapper.getObject());
如果(b){
final MyObject obj=新的MyObject(myObjectWrapper.getObject());
addObjectToDb(obj);
}
}
返回结果;
}
我应该如何有效地使此方法并发? 我认为“get”是安全的,但一旦我从缓存中获取值并更新缓存对象的字段,就会出现问题,因为另一个线程可能会获得相同的包装器并尝试更新相同的底层对象。。。我应该添加同步吗?如果是这样,那么我应该从“get”同步到循环结束迭代还是整个循环

当需要对循环中的映射键/值等执行更多操作时,也许有人可以分享一些更具体的正确有效使用ConcurrentHashMap的指导原则

我将非常感激

编辑: 该问题的一些背景: 我目前正在重构生产代码中的一些dao类,其中一些类使用HashMaps缓存从数据库检索的数据。所有使用缓存(用于写或读)的方法都将其全部内容放在同步(缓存)块中(是否安全?)。我在并发方面没有太多经验,我真的很想利用这个机会来学习。我天真地将HashMaps更改为ConcurrentHashMaps,现在希望在必要时删除同步bloock。所有缓存都用于写入和读取。本文介绍的方法是基于我所更改的方法之一,现在我正在尝试了解同步的时间和程度。clearAField方法只更改包装的POJO对象的一个字段的值,addObjectToDb尝试将该对象添加到数据库中

另一个示例是重新填充缓存:

public void findAll() throws SQLException{
    // get data from database into a list
    List<Data> data=getAllDataFromDatabase();
    cacheCHM.clear();
    cacheCHM.putAll(data);
} 
public void findAll()引发SQLException{
//将数据库中的数据获取到列表中
列表数据=getAllDataFromDatabase();
cacheCHM.clear();
cacheCHM.putAll(数据);
} 
在这种情况下,我应该将clear和putAll放在synchronize(cacheCHM)块中,对吗

我试图找到并阅读一些关于正确有效地使用CHM的帖子/文章,但大多数涉及单次操作,没有循环等。。。。我发现最好的是:

您没有提到您的应用程序中会发生什么并发,因此我假设您有多个线程调用
aMethod
,而没有其他调用

您只有一个对ConcurrentHashMap的调用:
myObjectsCache.get(id)
,这很好。事实上,由于没有任何东西将数据写入objectCache[请参见上面的假设],所以您甚至不需要ConcurrentHashMap!您可以使用任何不可变的集合。结尾有一行可疑的代码:
addObjectToDb(obj)
,此方法是否也会影响缓存?如果是这样的话,它仍然是安全的(可能,我们必须看到确定的方法),但是您肯定需要ConcurrenthashMap

危险在于您在此处更改对象:

myObjectWrapper.getObject().clearAField();
myObjectWrapper.getObject().setTime(System.currentTimeMillis());

多个线程可以同时对同一对象调用这些方法。如果不知道这些方法的作用,我们就不能说这是否安全。如果这些方法都被标记为同步,或者如果您注意确保这些方法同时运行是安全的,那么您就没事了(但是请注意,这些方法可以按不同的顺序运行,而不是按照您直观的预期!)。如果您不这么小心,则可能会导致数据损坏。

更好的线程安全和缓存方法是使用。以下是MyObjectSub-Cals如果是不可变的(不确定您为什么需要包装器-我会完全忽略这一点):

//Provided by way of example. You should consider generating these 
//using http://immutables.github.io/ or similar
public class MyImmutableObject {
    //If all members are final primitives or immutable objects 
    //then this class is threadsafe.
    final String field;
    final long time;

    public MyImmutableObject(String field, long time) {
        this.field = field;
        this.time = time;
    }

    public MyImmutableObject clearField() {
        //Since final fields can never be changed, our only option is to 
        //return a copy.
        return new MyImmutableObject("", this.time);
    }

    public MyImmutableObject setTime(long newtime) {
        return new MyImmutableObject(this.field, newtime);
    }
}
如果对象是不可变的,那么线程安全就简单多了。您的方法如下所示:

public List<Result> typicialCacheUsage(String key) {
    MyImmutableObject obj = myObjectsCache.get(key);

    obj = obj.clearField();
    obj = obj.setTime(System.currentTimeMillis());

    //If you need to put the object back in the cache you can do this:
    myObjectsCache.put(key, obj);

    List<Result> res = generateResultFromObject(obj);
    return res;
}
公共列表典型缓存用法(字符串键){
MyImmutableObject obj=myObjectsCache.get(键);
obj=obj.clearField();
obj=obj.setTime(System.currentTimeMillis());
//如果需要将对象放回缓存,可以执行以下操作:
myObjectsCache.put(键,对象);
List res=generateResultFromObject(obj);
返回res;
}

这在很大程度上取决于您的应用程序逻辑。如果您需要更新检索到的值以实现线程安全,则必须在对象本身上进行同步
ConcurrentHashMap
只保护映射本身的结构(即键与值的关系),而不保护包含的值。我同意@Jim的观点,ConcurrentMap只保护结构(即键与值的关系)。我想再添加一件事,根据上面的代码上下文,您只读取值ie.e myObjectsCache.get(id),因此为此,在调用map.put()之前,您甚至不需要并发映射。因此,我应该使用一个synchronize(myObjectWrapper)块,并将包装器上的所有操作放在其中,,或者我还需要将从缓存中检索对象放在syn中吗