以下是Java Concurrency in Practice中的摘录:
public class DelegatingVehicleTracker {
private final ConcurrentMap<String, Point> locations;
private final Map<String, Point> unmodifiableMap;
public DelegatingVehicleTracker(final Map<String, Point> points) {
this.locations = new ConcurrentHashMap<>(points);
this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
}
public Map<String, Point> getLocations() {
return unmodifiableMap;
}
public Point getLocation(final String id) {
return locations.get(id);
}
public void setLocation(final String id, final int x, final int y) {
if (null == locations.replace(id, new Point(x, y))) {
throw new IllegalArgumentException("Invalid vehicle name: " + id);
}
}
}
我的问题是关于使用setLocation
的{{1}}方法。这种方法的JavaDoc说它等同于:
ConcurrentMap.replace
除了动作以原子方式执行。
如果我们不使用原子版,会出现什么问题。一种可能是一个线程看到映射包含给定键,并且在为该键放置新值之前,另一个线程删除该键值对,但由于示例中的类不允许删除,因此不会发生这种情况。
另一种可能性是两个线程试图用不同的值替换相同的键。在这种情况下,一个线程可能不会返回正确的先前值,但在示例中我们不关心先前的值,方法if (map.containsKey(key)) {
return map.put(key, value);
} else return null;
返回setLocation
。
因此,似乎可以在没有void
的情况下重写该方法。这就是我的问题。在本书中同一类的后续版本中,与上面的版本几乎完全相同,方法replace
不使用setLocation
,只使用replace
,我想知道这是否会妥协线程安全。
答案 0 :(得分:1)
方法setLocation不使用replace,只是containsKey和I. 我想知道这是否会影响线程安全。
确实如此,你已经完美地描述了它
如果我们不使用原子版,会出现什么问题。一 可能性是一个线程看到地图包含给定的密钥 在为该键放入新值之前,另一个线程将删除 该键值对,但由于示例中的类不允许 删除,这不可能发生。
这就是为什么ConcurrentHashMap.replace的实现会锁定它试图替换的节点
/**
* Implementation for the four public remove/replace methods:
* Replaces node value with v, conditional upon match of cv if
* non-null. If resulting value is null, delete.
*/
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
答案 1 :(得分:1)
如果我们不使用原子版
,会出现什么问题
没有
这是风格。您可以以任何方式实现setLocation
方法,但恰好replace
是一种很好的方法,可以确保只有在地图中存在给定位置时才插入。
在本书中同一个类的后续版本中,几乎与上面的版本相同,方法setLocation不使用replace,只是containsKey,我想知道这是否会影响线程安全。
它不会损害线程安全性。密钥包含在映射中的断言不能在并发写入中更改,因为如果密钥存在,它将不会突然被删除,并且假设它不存在,则不会执行任何操作。
作者决定使用包含键,因为第二个示例使用可变点而不是不可变点。回想一下,JCIP是为Java 5编写的,当时并不存在computeIfPresent
等方法。因此,作者必须获取它自己的对象才能对其进行修改。因此,线程安全将被委托给可变点而不是车辆跟踪器本身。