2017-04-02 106 views
1

使用ConcurrentMap.replace点下面是从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,我在想这是否会影响线程安全。

回答

1

方法setLocation不使用替换,只是中的containsKey和我 想知道,这可能会影响线程安全。

确实如此,你已经完全

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

这就是为什么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; 
    } 
0

什么可能出问题,如果我们不使用原子弹版本

没有节点。

它的风格。您可以以任何方式实施setLocation方法,但恰巧replace是确保只有在给定位置存在于地图中时才插入的好方法。

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

它不损害线程安全。关键字包含在映射中的断言不能在并发写入中更改,因为如果存在该关键字,它将不会被突然删除,并且假定它不存在,则不会执行任何操作。

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

相关问题