何时以及如何使用ConcurrentHashMap的其他同步?

时间:2016-06-09 15:38:30

标签: java multithreading concurrenthashmap

在使用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;
    }

我应该如何有效地使这个方法并发? 我认为&#34; get&#34;是安全的但是一旦我从缓存中获取值并更新缓存对象的字段 - 可能会有问题,因为另一个线程可以获得相同的包装并尝试更新相同的底层对象...我应该添加同步吗?如果是这样,那么我应该从&#34; get&#34;循环迭代结束还是整个循环?

当需要对循环中的地图键/值等进行更多操作时,也许有人可以分享一些更具体和有效使用ConcurrentHashMap的指南......

我真的很感激。

修改 该问题的一些背景: 我目前正致力于在生产代码中重构一些dao类,并且一些类使用HashMaps来缓存从数据库中检索的数据。使用缓存(用于写入或读取)的所有方法都将其整个内容放在同步(缓存)块中(播放安全吗?)。我没有太多的并发经验,我真的想借此机会学习。我天真地将HashMaps更改为ConcurrentHashMaps,现在想要删除他们需要的同步bloocks。所有缓存都用于写入和读取。所提出的方法基于我已经改变的方法之一,现在我试图了解何时以及在何种程度上同步。方法clearAField只是更改包装的POJO对象的一个​​字段的值,addObjectToDb尝试将对象添加到数据库。

其他例子是重新填充缓存:

public void findAll() throws SQLException{
    // get data from database into a list
    List<Data> data=getAllDataFromDatabase();
    cacheCHM.clear();
    cacheCHM.putAll(data);
} 

在这种情况下,我应该将clear和putAll放在同步(cacheCHM)块中,对吧?

我试图找到并阅读一些关于CHM正确有效使用的帖子/文章,但大多数都涉及单一操作,没有循环等......我发现的最好的是: http://www.javamadesoeasy.com/2015/04/concurrenthashmap-in-java.html

2 个答案:

答案 0 :(得分:1)

你没有提到你期望在你的应用程序中发生什么并发,所以我假设你有多个线程调用aMethod,而没有别的

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

危险在于你改变对象的地方,在这里:

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

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

答案 1 :(得分:0)

更好的threadaftey和缓存方法是使用immutable objects。以下是MyObjectSub calss在不可变的情况下的样子[不确定为什么需要包装器 - 我完全可以省略它]:

//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;
}