在此示例中使用ConcurrentMap.replace有什么意义

时间:2017-04-02 10:03:44

标签: java multithreading thread-safety concurrenthashmap

以下是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,我想知道这是否会妥协线程安全。

2 个答案:

答案 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等方法。因此,作者必须获取它自己的对象才能对其进行修改。因此,线程安全将被委托给可变点而不是车辆跟踪器本身。