【问题标题】:Java Map synchronizationJava 地图同步
【发布时间】:2014-01-28 21:03:37
【问题描述】:
我正在做一些我定义了 Collections.synchronizedMap 的事情。
在一个线程A中,它只会获取值,并放入一个更新的值,所以这不是问题。
在另一个线程 B 中,它将遍历条目,然后根据条目的值,进行一些比较,如果匹配某个条件,它将从映射中删除该条目。
我的理解是同步的 Map 会阻止访问。
但是是否有可能线程 B 获得一个条目,然后线程 A 更新该值,然后线程 B 删除该值,因为它匹配某些条件,但它不应该因为值线程 A 更新它。解决此问题的最佳实施是什么?
【问题讨论】:
标签:
java
multithreading
synchronization
synchronized
【解决方案1】:
听起来您只需要同步“获取、检查和删除”部分。例如:
Iterable<String> keys;
synchronized (map) {
keys = new ArrayList<>(map.keySet());
}
for (String key : keys) {
synchronized (map) {
Integer value = map.get(key);
// The other thread won't be able to update the value at this point
if (value != null && value < 0) {
map.remove(key);
}
}
}
您的更新线程可能需要做同样的事情 - 从您的描述中不清楚它是否需要“看到”删除。
【解决方案3】:
这可以使用ConcurrentHashMap 完全不阻塞地完成,如下所示:
final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(); // creation
map.put("aKey", "aValue"); // example content
// Thread A:
map.replace("aKey", "aNewValue"); // atomic replace call
// Thread B:
for (String key : map.keySet()) {
do {
processed = true;
final String value = map.get(key);
if ((value != null) && shouldRemove(value)) { // some comparison or check
if (map.remove("aKey", value) == false) { // is value still the same?
processed = false; // value has been changed in between, try again
}
}
} while (processed == false);
}
这是线程安全的,因为线程 B 只删除值(原子地),如果它没有被改变。如果两者之间确实发生了变化,线程 B 将再次尝试并使用新值进行比较。这类似于其他原子类中使用的compareAndSet 的概念。
如果线程 A 很少更改,线程 B 将执行几乎与非同步映射相同的速度。如果线程 A 做了很多更改,线程 B 中可能会有多次重复调用,具体取决于进行比较需要多长时间以及值更改的频率。但在所有情况下,这都是线程安全的,并且比任何同步实现都更快。
【解决方案4】:
听起来您正在尝试实现缓存。那里有许多好的并发缓存实现。我相信guava 的实现相当不错。
【解决方案5】:
首先关于解决方案。我认为最好的解决方案是标准的 ConcurrentHashMap。它具有用于删除的 CAS 操作,如下所示:
boolean remove(Object key, Object value)
Removes the entry for a key only if currently mapped to a given value.
希望对你有帮助。
现在第二件事是关于“我的理解是同步的地图会阻止访问。”我认为这不是完整的理解,请参阅以下同步地图 javadoc 的详细信息:
* It is imperative that the user manually synchronize on the returned
* map when iterating over any of its collection views:
* <pre>
* Map m = Collections.synchronizedMap(new HashMap());
* ...
* Set s = m.keySet(); // Needn't be in synchronized block
* ...
* synchronized (m) { // Synchronizing on m, not s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>