我的程序由许多类组成。我有两个类的交互问题 - WebDataCache和Client。问题类列在下面。
WEBDATA:
这只是一个数据类,表示从互联网上检索到的一些数据
的 WebService的:
这只是一个Web服务包装类,它连接到特定的Web服务,读取一些数据并将其存储在WebData类型的对象中。
的 WebDataCache:
这是一个使用WebService类来检索缓存在地图中的数据的类,该数据由数据的ids字段键入。
的客户端:
这是一个包含对WebDataCache类实例的引用并使用缓存数据的类。
问题是(如下图所示)当类循环缓存数据时,WebDataCache可以更新底层集合。
我的问题是如何同步对缓存的访问?
我不想同步整个缓存,因为Client类有多个实例,但每个实例都有一个唯一的id(即新的Client(0,...),new Client(1,... ),新客户端(2,...)等每个实例仅对客户端所实现的id所键入的数据感兴趣。
我可以使用任何相关的设计模式吗?
class WebData {
private final int id;
private final long id2;
public WebData(int id, long id2) {
this.id = id;
this.id2 = id2;
}
public int getId() { return this.id; }
public long getId2() { return this.id2; }
}
class WebService {
Collection<WebData> getData(int id) {
Collection<WebData> a = new ArrayList<WebData>();
// populate A with data from a webservice
return a;
}
}
class WebDataCache implements Runnable {
private Map<Integer, Map<Long, WebData>> cache =
new HashMap<Integer, Map<Long, WebData>>();
private Collection<Integer> requests =
new ArrayList<Integer>();
@Override
public void run() {
WebService webSvc = new WebService();
// get data from some web service
while(true) {
for (int id : requests) {
Collection<WebData> webData = webSvc.getData(id);
Map<Long, WebData> row = cache.get(id);
if (row == null)
row = cache.put(id, new HashMap<Long, WebData>());
else
row.clear();
for (WebData webDataItem : webData) {
row.put(webDataItem.getId2(), webDataItem);
}
}
Thread.sleep(2000);
}
}
public synchronized Collection<WebData> getData(int id){
return cache.get(id).values();
}
public synchronized void requestData(int id) {
requests.add(id);
}
}
-
class Client implements Runnable {
private final WebDataCache cache;
private final int id;
public Client(int id, WebDataCache cache){
this.id = id;
this.cache = cache;
}
@Override
public void run() {
cache.requestData(id);
while (true) {
for (WebData item : cache.getData(id)) {
// java.util.ConcurrentModificationException is thrown here...
// I understand that the collection is probably being modified in WebDataCache::run()
// my question what's the best way to sychronize this code snippet?
}
}
}
}
谢谢!
答案 0 :(得分:5)
使用java.util.concurrent.ConcurrentHashMap而不是普通的旧java.util.HashMap。来自Javadoc:
支持完整的哈希表 检索和并发 可调节的预期并发性 更新。这门课也服从同样的道理 功能规范为Hashtable, 并包括方法的版本 对应于每种方法 哈希表。但是,尽管如此 操作是线程安全的,检索 操作不需要锁定,并且 没有任何锁定支持 整个表格的方式 阻止所有访问。这堂课是 与Hashtable完全互操作 依赖于其线程的程序 安全但不同步 的信息。
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentHashMap.html
所以你要替换:
private Map<Integer, Map<Long, WebData>> cache =
new HashMap<Integer, Map<Long, WebData>>();
使用
private Map<Integer, Map<Long, WebData>> cache =
new ConcurrentHashMap<Integer, Map<Long, WebData>>();
答案 1 :(得分:3)
我最好的建议是使用现有的缓存实施,例如JCS或EhCache - 这些是经过实战考验的实施。
否则,您的代码中会发生一些事情。可能会以有趣的方式打破的事情。
java.util.concurrent.ConcurrentHashMap
。java.util.concurrent
中更安全的列表实现,要么确保 all 访问它正在同步相同的锁。如果你想阅读这本书的书,我可以推荐Brian Goetz的Java Concurrency in Practice。
答案 2 :(得分:2)
除了其他发布的建议之外,还要考虑缓存更新的频率与刚刚读取的频率。如果读数占主导地位并且更新很少,并且读取循环能够立即看到每个更新并不重要,请考虑使用CopyOnWriteArraySet
。它和它的兄弟CopyOnWriteArrayList
允许同时阅读和更新成员;读者可以看到一致的快照,不受基础集合任何突变的影响 - 类似于关系数据库中的 SERIALIZABLE 隔离级别。
这里的问题是,这两个结构都没有为您提供开箱即用的字典或关联数组存储(la Map
)。您必须定义一个复合结构来将键和值存储在一起,并且,鉴于CopyOnWriteArraySet
使用Object#equals()
进行成员资格测试,您必须编写一个非常规的基于密钥的{{1你的结构的方法。
答案 3 :(得分:1)
LES2的答案很好,除非您必须替换:
row = cache.put(id, new HashMap<Long, WebData>());
使用:
row = cache.put(id, new ConcurrentHashMap<Long, WebData>());
对于那个持有“有问题”集合而不是整个缓存的那个。
答案 4 :(得分:0)
您可以对最终拥有正在共享的集合的缓存返回的row
进行同步。
在WebDataCache上:
Map<Long, WebData> row = cache.get(id);
if (row == null) {
row = cache.put(id, new HashMap<Long, WebData>());
} else synchronized( row ) {
row.clear();
}
for (WebData webDataItem : webData) synchronized( row ) {
row.put(webDataItem.getId2(), webDataItem);
}
// it doesn't make sense to synchronize the whole cache here.
public Collection<WebData> getData(int id){
return cache.get(id).values();
}
在客户端:
Collection<WebData> data = cache.getData(id);
synchronized( data ) {
for (WebData item : cache.getData(id)) {
}
}
当然,这远非完美,只是回答了要同步的问题。在这种情况下,可以访问缓存中的row.clear
row.put
中的底层集合以及客户端上的迭代。