我正在尝试使用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;
}
}
答案 0 :(得分:2)
如果多个线程可以写入给定的List
(应该是List<Integer>
,因为您要向其添加int
),您需要同步某些内容。但是,您不需要全局锁定。此外,您似乎认为Cache
和ConcurrentHashMap
会复制您放入其中的对象并从中获取,因为一旦更新它们就会再次放置它们,但它们不会:它们保留对你投入的内容的引用。
我会以这种方式更改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();
}
}
}