包含列表的高速缓存ConcurrentHashMaps的线程安全性

时间:2012-09-13 04:21:39

标签: java thread-safety guava

我正在尝试使用Google Guava Cache创建ConcurrentHashMaps的线程安全单例缓存。这些地图中的每一个都将包含一个List。只有在可以添加的所有线程都已执行后,List才会被读取。我想知道我的实现(特别是我更新项目的位置)是否是线程安全的/如何改进它。如果没有使用同步块,有没有更好的方法呢?

public enum MyCache {
    INSTANCE;

    private static Cache<Integer, ConcurrentHashMap<String, List>> cache = 
        CacheBuilder.newBuilder()
            .maximumSize(1000)
            .build();

    private static AtomicInteger uniqueCount = new AtomicInteger(0);

    private final Object mutex = new Object();

        //Create a new unique ConcurrentHashMap
    public Integer newMapItem(){

        Integer key = uniqueCount.incrementAndGet();

        //We dont care if something exists
        cache.put(
            key,
            new ConcurrentHashMap<String, List>()
        );


        return key;
    }

    public void expireMapItem(int key){
        cache.invalidate(key);
    }

    public Integer add(int cacheKey, String mapListKey, int value){

        synchronized(mutex){
            ConcurrentMap<String, List> cachedMap = cache.getIfPresent(cacheKey);
            if (cachedMap == null){
                //We DONT want to create a new map automatically if it doesnt exist
                return null;
            }


            List mappedList = cachedMap.get(mapListKey);

            if(mappedList == null){



                List newMappedList = new List();
                mappedList = cachedMap.putIfAbsent(mapListKey, newMappedList);
                if(mappedList == null){
                    mappedList = newMappedList;
                }
            }
            mappedList.add(value);
            cachedMap.replace(mapListKey, mappedList);

            cache.put(
                cacheKey,
                cachedMap
            );

        }
        return value;
    }



}

public enum MyCache { INSTANCE; private static Cache<Integer, ConcurrentHashMap<String, List>> cache = CacheBuilder.newBuilder() .maximumSize(1000) .build(); private static AtomicInteger uniqueCount = new AtomicInteger(0); private final Object mutex = new Object(); //Create a new unique ConcurrentHashMap public Integer newMapItem(){ Integer key = uniqueCount.incrementAndGet(); //We dont care if something exists cache.put( key, new ConcurrentHashMap<String, List>() ); return key; } public void expireMapItem(int key){ cache.invalidate(key); } public Integer add(int cacheKey, String mapListKey, int value){ synchronized(mutex){ ConcurrentMap<String, List> cachedMap = cache.getIfPresent(cacheKey); if (cachedMap == null){ //We DONT want to create a new map automatically if it doesnt exist return null; } List mappedList = cachedMap.get(mapListKey); if(mappedList == null){ List newMappedList = new List(); mappedList = cachedMap.putIfAbsent(mapListKey, newMappedList); if(mappedList == null){ mappedList = newMappedList; } } mappedList.add(value); cachedMap.replace(mapListKey, mappedList); cache.put( cacheKey, cachedMap ); } return value; } }

1 个答案:

答案 0 :(得分:2)

如果多个线程可以写入给定的List(应该是List<Integer>,因为您要向其添加int),您需要同步某些内容。但是,您不需要全局锁定。此外,您似乎认为CacheConcurrentHashMap会复制您放入其中的对象并从中获取,因为一旦更新它们就会再次放置它们,但它们不会:它们保留对你投入的内容的引用。

我会以这种方式更改add()方法:

public Integer add(int cacheKey, String mapListKey, int value) {
    // You don't need to synchronize here, since the creation of the map is not
    // synchronized. So either it has been created before, or it hasn't, but there
    // won't be a concurrency problem since Cache is thread-safe.
    ConcurrentMap<String, List<Integer>> cachedMap = cache.getIfPresent(cacheKey);
    if (cachedMap == null){
        // We DON'T want to create a new map automatically if it doesn't exist
        return null;
    }

    // CHM is of course concurrent, so you don't need a synchronized block here
    // either.
    List<Integer> mappedList = cachedMap.get(mapListKey);
    if (mappedList == null) {
        List<Integer> newMappedList = Lists.newArrayList();
        mappedList = cachedMap.putIfAbsent(mapListKey, newMappedList);
        if (mappedList == null) {
            mappedList = newMappedList;
        }
    }

    // ArrayList is not synchronized, so that's the only part you actually need to
    // guard against concurrent modification.
    synchronized (mappedList) {
        mappedList.add(value);
    }

    return value;
}

实际上,我创建的Cache LoadingCache代替了Cache ConcurrentHashMap,它使add()中的代码更简单,将List的创建移至CacheLoader实施。您仍然可以使用LoadingCache方法将Map公开为asMap()。我也删除了一些装箱/拆箱。

编辑:将add()的返回类型更改为boolean,而不是int,这与原始return null无关(当返回类型为Integer)。不需要潜在的NPE。

public enum MyCache {
    INSTANCE;

    private static Cache<Integer, LoadingCache<String, List<Integer>>> cache =
            CacheBuilder.newBuilder()
                    .maximumSize(1000)
                    .build();

    private static AtomicInteger uniqueCount = new AtomicInteger(0);

    public int newMapItem() {
        int key = uniqueCount.incrementAndGet();

        //We dont care if something exists
        cache.put(key, CacheBuilder.newBuilder().build(ListCacheLoader.INSTANCE));

        return key;
    }

    public void expireMapItem(int key) {
        cache.invalidate(key);
    }

    public boolean add(int cacheKey, String mapListKey, int value) {
        // You don't need to synchronize here, since the creation of the map is not
        // synchronized. So either it has been created before, or it hasn't, but there
        // won't be a concurrency problem since Cache is thread-safe.
        LoadingCache<String, List<Integer>> cachedMap = cache.getIfPresent(cacheKey);
        if (cachedMap == null) {
            // We DON'T want to create a new map automatically if it doesn't exist
            return false;
        }

        List<Integer> mappedList = cachedMap.getUnchecked(mapListKey);

        // ArrayList is not synchronized, so that's the only part you actually need to
        // guard against concurrent modification.
        synchronized (mappedList) {
            mappedList.add(value);
        }

        return true;
    }

    private static class ListCacheLoader extends CacheLoader<String, List<Integer>> {
        public static final ListCacheLoader INSTANCE = new ListCacheLoader();

        @Override
        public List<Integer> load(String key) {
            return Lists.newArrayList();
        }
    }
}