2

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

该解决方案由多个线程有效地访问,我定义了以下内容;

ConcurrentSkipListMap<key, ConcurrentSkipListSet<values>>

我的问题是当值集大小为 0 时删除键是否需要同步?我知道这两个类是“并发的”,并且我已经查看了 OpenJDK 源代码,但是在一个线程 T1 检查 Set 是否为空并在 remove(...) 中删除 Map 之间似乎有一个窗口另一个线程 T2 调用 add(...)。结果是 T1 删除了最后一个 Set 条目并删除了与 T2 交错的 Map,只需添加一个 Set 条目。因此 Map 和 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;
        }
    }
}
4

1 回答 1

0

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

  • 一个线程可以在if (endpoints.size() == 0)和之间添加remove(id);- 你看到了
  • in add,一个线程可以读取一个非空值EndpointSet endpoints = get(id);,另一个线程可以从该集合中删除数据,从映射中删除该集合,因为该集合为空。然后,初始线程将向集合添加一个值,该值不再保存在映射中 => 数据也会丢失,因为它变得无法访问。

解决问题的最简单方法是同时添加和删除synchronized。但是您会失去使用ConcurrentMap.

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

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

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

于 2012-08-15T10:49:17.163 回答