2012-08-15 54 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的所有性能优势。

或者,您可以将空集保留在地图中 - 除非您有内存限制。您仍然需要某种形式的同步,但优化起来会更容易。

如果争用(性能)是一个问题,您可以通过同步键或值来尝试更细化的锁定策略,但它可能非常棘手(并且由于字符串池的原因,锁定字符串并不是一个好主意)。

看来,在所有情况下,您都可以使用非并发集,因为您需要自行同步它。