java.util.concurrent:external synchronize删除map值

时间:2012-08-15 10:27:07

标签: java

我需要跟踪唯一键的多个值,即1(a,b)2(c,d)等......

解决方案由多个线程访问,因此我有以下定义;

ConcurrentSkipListMap<key, ConcurrentSkipListSet<values>>

我的问题是当值集大小为0时是否需要同步删除密钥?我知道这两个类是“并发”的,我查看了OpenJDK源代码,但是我看来在一个线程T1之间有一个窗口,检查Set是空的,并删除删除中的Map(...)和另一个线程T2调用add(...)。结果是T1删除最后一个Set条目并删除与T2交错的Map只添加一个Set条目。因此,T1和T2 Set条目将被T1删除,数据将丢失。

我只是“同步”add()和remove()方法还是有“更好”的方式?

Map由多个线程修改,但只能通过两种方法修改。

代码段如下;

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

1 个答案:

答案 0 :(得分:0)

因为它是你的代码有数据竞争。可能发生的事情的例子:

  • 线程可以在if (endpoints.size() == 0)remove(id);之间添加 - 您看到了
  • add中,一个线程可以读取EndpointSet endpoints = get(id);中的非空值,另一个线程可以从该集合中删除数据,从地图中删除该集合,因为该集合为空。然后,初始线程将向集合中添加一个值,该值不再在地图中保存=&gt;数据因无法访问而丢失。

解决问题的最简单方法是同时添加和删除synchronized。但是,您将失去使用ConcurrentMap的所有性能优势。

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

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

似乎在所有情况下,您都可以使用非并发集,因为您需要自己在外部进行同步。