并发性如何进行同步以防止java冲突。util。ConcurrentModificationException
我有一个由许多课程组成的课程。我对其中两个类(WebDataCache和Client)的交互有问题。下面列出了问题类
网络数据:
这只是一个数据类,表示从internet检索到的一些数据
网络服务:
这只是一个web服务包装类,它连接到特定的web服务,读取一些数据并将其存储在WebData类型的对象中
WebDataCache:
这是一个使用WebService类检索缓存在映射中的数据的类,由数据的ID字段设置关键字
客户端:
这是一个包含对WebDataCache类实例的引用并使用缓存数据的类
问题是(如下所示),当类在缓存数据中循环时,WebDataCache可能会更新基础集合
我的问题是如何同步对缓存的访问
我不想同步整个缓存,因为客户端类有多个实例,但是每个实例都用唯一的id实例化(即新客户端(0,…),新客户(1,…),新客户(2,…),etc每个实例只对客户机实例所用的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?
}
}
}
}
谢谢
# 1 楼答案
来自LES2的答案很好,只是您必须替换:
与:
因为它保存的是“有问题”的集合,而不是整个缓存
# 2 楼答案
我的最佳建议是使用现有的缓存实现,如JCS或EhCache——这些都是经过战斗测试的实现
否则,代码中会出现一些问题。可以以有趣的方式打破的东西
java.util.concurrent.ConcurrentHashMap
李>java.util.concurrent
更改为更安全的列表实现,或者确保对它的所有访问都在相同的锁上同步李>如果你想读一本关于这方面的书,我可以推荐Brian Goetz的《Java并发实践》
# 3 楼答案
使用java。util。同时发生的ConcurrentHashMap代替了普通的旧java。util。哈希映射。从Javadoc:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentHashMap.html
因此,您将替换:
与
# 4 楼答案
您可以在缓存返回的
row
上进行同步,该缓存位于保存正在共享的集合的末尾在WebDataCache上:
在客户端:
当然,这远不是完美的,它只是回答了同步什么的问题。在这种情况下,可以访问中的参考底图集合^缓存上的{}
row.put
和客户端上的迭代顺便说一句,为什么缓存中有一个映射,而在客户端使用一个集合。您应该在两者上使用相同的结构,并且不公开底层实现
# 5 楼答案
除了其他发布的建议之外,还要考虑缓存的更新与读取的频繁程度。如果阅读占主导地位,更新是罕见的,并不是关键的阅读循环能够看到每一个更新立即,考虑使用^{} 。它及其同级^{} 允许同时读取和更新成员;读者可以看到一个一致的快照,它不受底层集合的任何变化的影响——类似于关系数据库中的可序列化隔离级别
然而,这里的问题是,这两种结构都没有提供现成的字典或关联数组存储(la
Map
)。您必须定义一个复合结构来将键和值存储在一起,并且,鉴于CopyOnWriteArraySet
使用Object#equals()
进行成员资格测试,您必须为您的结构编写一个非常规的基于键的equals()
方法