线程安全查找和删除集合

时间:2017-06-18 13:02:06

标签: java multithreading collections thread-safety java.util.concurrent

我想知道如何做以下的事情

class MyCollection<E> implements Collection<E> {
    @Nullable E findAndRemove(Predicate<E> predicate) {
        for (E e : this) {
            if (predicate.test(e)) {
                remove(e);
                return e;
            }
        }
        return null;
    }   
}

以线程安全的方式。它实际上不必是Collection,因为唯一需要的操作是addfindAndRemove。注意

  • removeIf不相关,因为它删除所有匹配元素并且不提供任何回报
  • 让我们假设同步不够好
  • CopyOnWriteArrayList可能会这样做,但是Javadoc说“有效的时候...遍历操作的数量远远超过突变”,这绝对不是真的。
  • 不允许两次返回相同的元素(除非在此期间重新添加),这在使用CopyOnWriteArrayList天真时很容易发生
  • 返回哪个匹配元素无关紧要

关于过早的优化和XY问题:是的,我知道!当我在一天中可能或不需要的东西鬼混时,我进入了这个,但我发现这个问题本身很有趣。

2 个答案:

答案 0 :(得分:2)

由于您只需要addfindAndRemove方法,因此某些类型的并发哈希是自然的选择,因为自Java 1.5以来已经有了很好的实现(ConcurrentHashMap)。既然我们实际上不需要Map而是Set我们可以使用(因为Java 8无论如何)ConcurrentHashMap.newKeySet()来创建一个并发集,使用与并发地图。

然后,给定并发集,我们几乎可以使用上面的循环,并乐观地删除元素,然后继续搜索失败(这意味着一个线程同时删除了匹配的元素):

class MyCollection<E> {

  private Set<E> underlying = ConcurrentHashMap.newKeySet();

  void add(E elem) {
    underlying.add(elem);
  }

  @Nullable E findAndRemove(Predicate<E> predicate) {
    for (E e : underlying) {
        if (predicate.test(e) && remove(e)) {
          return e;
        }
    }
    return null;
  }   
}

与您的示例代码相关的唯一真正变化是我们检查Set.remove()的结果,以查看该元素是否实际已删除。对于并发集,这可以安全地使用#34; - 也就是说,只有实际删除了对象的线程才会看到true,所以这个集合只有在实际删除时才会正确返回该元素,然后其他线程就无法返回该元素。

它应该满足您的所有要求并执行以及基础并发映射实现,这在现代JDK上非常好&#34;。

请注意,使用Set意味着不允许使用重复的元素。从您的描述中可以清楚地知道您是否计划支持重复项,但如果您这样做,则可以使用基于并发多图 1 构建的相同方法,或者仅使用{{1其中ConcurrentHashMap<E, AtomicInteger>值是具有相同键的元素数量的引用计数,AtomicIntegeradd方法操纵引用计数 2

1 然而,在快速搜索中,我无法找到并发多图的明显开源实现。请注意,您实际上并不需要具有其所有功能的大写M findAndRemove实现 - 您实际上只需要&#34;某些多重集合&#34;能够添加元素,迭代可用元素,并且&#34;删除&#34;一个元素(即,减少其在集合中的引用计数)。

2 我实际上掩盖了引用计数实现的一些细节,因为在这种情况下,在将refcount递减为零的线程和任何调用的线程之间可能存在争用{ {1}}表示可能将引用计数增加到零以上的相同元素(但该条目已被删除)。这是可以避免的,但我还没有详细说明,因为如果你想支持重复,我不清楚。

答案 1 :(得分:1)

您可以对集合中的变异使用读写锁定,也可以使用ConcurrentHashMap来表示集合。

Set set =Collections.newSetFromMap(new ConcurrentHashMap<Object,Boolean>());