【问题标题】:What is the point of using ConcurrentMap.replace in this example在这个例子中使用 ConcurrentMap.replace 有什么意义
【发布时间】:2017-04-02 10:03:44
【问题描述】:

以下是 Java 并发实践的摘录:

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);
        }
    }
}

我的问题是关于使用ConcurrentMap.replacesetLocation 方法。这个方法的JavaDoc说它相当于:

if (map.containsKey(key)) {
     return map.put(key, value);
} else return null;

除了动作是原子执行的。

如果我们不使用原子版本,可能会出现什么问题。一种可能性是,一个线程看到映射包含给定的键,并且在为该键放置新值之前,另一个线程删除了该键值对,但由于示例中的类不允许删除,因此不会发生这种情况。

另一种可能性是两个线程试图用不同的值替换同一个键。在这种情况下,一个线程可能不会返回正确的前一个值,但在示例中我们不关心前一个值,方法setLocation 返回void

因此,似乎可以在没有replace 的情况下重写该方法。这就是我提出问题的原因。在本书同一类的后续版本中,与上面的几乎相同,方法setLocation 不使用replace,只使用containsKey,我想知道这是否会危及线程安全。

【问题讨论】:

    标签: java multithreading thread-safety concurrenthashmap


    【解决方案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;
        }
    

    【讨论】:

      【解决方案2】:

      如果我们不使用原子版本会出什么问题

      什么都没有。

      这是风格。您可以以任何您想要的方式实现setLocation 方法,但碰巧replace 是确保您仅在地图中存在给定位置时才插入的好方法。

      在本书同一类的后续版本中,几乎与上面的相同,方法 setLocation 不使用 replace,只使用 containsKey,我想知道这是否会危及线程安全。

      它不会影响线程安全。密钥包含在映射中的断言不能在并发写入中更改,因为鉴于密钥存在,它不会突然被删除,并且如果它不存在,则不会做任何事情。

      作者决定使用 contains 键,因为第二个示例使用可变点而不是不可变点。回想一下,JCIP 是为 Java 5 编写的,当时不存在 computeIfPresent 这样的方法。因此,作者必须自己获取对象才能对其进行修改。因此,线程安全将被委托给可变点而不是车辆跟踪器本身。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-01
        • 2015-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多