2012-08-15 15 views
2

我需要保持多個值的軌道對唯一的密鑰,即1(A,B)圖2(c,d)等...java.util.concurrent中:外部同步以去除映射值

的解決方案是由多個線程有效地訪問我有以下定義;

ConcurrentSkipListMap<key, ConcurrentSkipListSet<values>> 

我的問題是當值集大小爲0時刪除鍵是否需要同步?我知道這兩個類是「併發」的,我查看了OpenJDK的源代碼,但是我看到在一個線程T1之間出現一個窗口,檢查Set是否爲空,並刪除了remove(...)另一個線程T2調用add(...)。結果是T1刪除最後一個Set條目並刪除與T2交錯的Map,只是添加一個Set條目。因此,Map和T2 Set條目被T1刪除,數據丟失。

我只是「同步」add()和remove()方法,還是有「更好」的方法?

該地圖由多個線程修改,但只能通過兩種方法修改。

代碼片段如下;

protected static class EndpointSet extends U4ConcurrentSkipListSet<U4Endpoint> { 
    private static final long serialVersionUID = 1L; 
    public EndpointSet() { 
     super(); 
    } 
} 

protected static class IDToEndpoint extends U4ConcurrentSkipListMap<String, EndpointSet> { 
    private static final long serialVersionUID = 1L; 
    protected Boolean add(String id, U4Endpoint endpoint) { 
     EndpointSet endpoints = get(id); 
     if (endpoints == null) { 
      endpoints = new EndpointSet(); 
      put(id, endpoints); 
     } 
     endpoints.add(endpoint); 
     return true; 
    } 

    protected Boolean remove(String id, U4Endpoint endpoint) { 
     EndpointSet endpoints = get(id); 
     if (endpoints == null) { 
      return false; 
     } else { 
      endpoints.remove(endpoint); 
      if (endpoints.size() == 0) { 
       remove(id); 
      } 
      return true; 
     } 
    } 
} 

回答

0

因爲它是你的代碼有數據競賽。什麼例子可能發生:

  • 一個線程可以if (endpoints.size() == 0)remove(id);之間添加 - 你看到
  • add,一個線程可以在EndpointSet endpoints = get(id);讀取非空值,而另一個線程可以從該組刪除數據,因爲這個集合是空的,所以從集合中移除集合。然後初始線程會向該集合添加一個值,該集合不會再保存在地圖中,否則數據會因爲無法訪問而丟失。

解決問題的最簡單方法是同時添加和刪除​​。但是,您因此失去了使用ConcurrentMap的所有性能優勢。

或者,您可以將空集保留在地圖中 - 除非您有內存限制。您仍然需要某種形式的同步,但優化起來會更容易。

如果爭用(性能)是一個問題,您可以通過同步鍵或值來嘗試更細化的鎖定策略,但它可能非常棘手(並且由於字符串池的原因,鎖定字符串並不是一個好主意)。

看來,在所有情況下,您都可以使用非併發集,因爲您需要自行同步它。