简体   繁体   English

线程安全查找和从集合中删除对象

[英]Thread-safe find and remove an object from a Collection

I wonder how can I do something like the following 我想知道如何做以下事情

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

in a thread-safe manner. 以线程安全的方式。 It doesn't really have to be a Collection , as the only needed operations are add and findAndRemove . 它实际上不一定是Collection ,因为唯一需要的操作是addfindAndRemove Note that 注意

  • removeIf is unrelated as it removes all matching elements and gives nothing in return removeIf是无关的,因为它删除了所有匹配的元素,并且没有任何回报
  • let's assume that synchronization isn't good enough 我们假设同步不够好
  • CopyOnWriteArrayList might do, but it's Javadoc says "efficient when ... traversal operations vastly outnumber mutations", which definitely isn't true here CopyOnWriteArrayList可能会这样做,但是它的Javadoc说“当...遍历操作大大超过突变时效率很高”,这在这里肯定不是真的
  • it's not allowed to return the same element twice (unless re-added in the meantime), which could easily happen when using CopyOnWriteArrayList naively 它不允许两次返回相同的元素(除非在此期间重新添加),这在使用CopyOnWriteArrayList时很容易发生
  • which matching element gets returned doesn't matter 返回哪个匹配元素无关紧要

Concerning premature optimization and XY problem: Yes, I know! 关于过早优化和XY问题: 是的,我知道! I went into this when fooling around with something I may or mayn't need one day, but I find this problem interesting per se. 当我在某天可能或不需要的东西上鬼混时,我进入了这个,但我发现这个问题本身很有趣。

Since you only need the add and findAndRemove methods, some type of concurrent hash is a natural choice, as there has been a good implementation ( ConcurrentHashMap ) since Java 1.5. 由于您只需要addfindAndRemove方法,因此某些类型的并发哈希是一种自然的选择,因为自Java 1.5以来就有一个很好的实现( ConcurrentHashMap )。 Now since we don't actually need a Map but rather a Set we can just use (since Java 8 anyway) ConcurrentHashMap.newKeySet() to create a concurrent set using the same implementation as the concurrent map. 现在因为我们实际上并不需要Map而是一个Set我们可以使用(因为Java 8无论如何) ConcurrentHashMap.newKeySet()使用与并发映射相同的实现来创建并发集。

Then, given a concurrent set, we can pretty much use your loop above, and optimistically remove the element, and then simply continue searching on failure (which means that a thread concurrently removed the element that matched): 然后,给定并发集,我们几乎可以使用上面的循环,并乐观地删除元素,然后继续搜索失败(这意味着一个线程同时删除了匹配的元素):

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

The only real change with respect to your example code is that we check the result of Set.remove() to see if the element was actually removed. 关于示例代码的唯一真正的变化是我们检查Set.remove()的结果以查看该元素是否实际被删除。 For the concurrent set, this works "safely" - ie, only the thread which actually removed the object will see true , so this set will correctly return the element only if it was actually removed, and no other thread will then be able to return that element. 对于并发集,这可以“安全地”工作 - 也就是说,只有实际删除了对象的线程才会看到true ,所以这个集合只有在实际被删除时才会正确返回该元素,并且其他线程才能返回那个元素。

It should satisfy all your requirements and perform as well as the underlying concurrent map implementations, which on modern JDKs is "very well". 它应该满足您的所有要求和执行以及基础并发映射实现,这在现代JDK上“非常好”。

Note that the use of a Set implies that duplicate elements aren't allowed. 请注意,使用Set意味着不允许使用重复元素。 It wasn't clear from your description whether you planned to support duplicates or not, but if you did, you could use the same approach built on a concurrent multimap 1 , or simply using ConcurrentHashMap<E, AtomicInteger> , where the AtomicInteger value a reference count of the number of elements with the same key, the add and findAndRemove methods manipulate the reference count 2 . 从您的描述中不清楚您是否计划支持重复项,但如果您这样做,您可以使用基于并发多重映射1的相同方法,或者只使用ConcurrentHashMap<E, AtomicInteger> ,其中AtomicInteger值为a引用计数具有相同键的元素数, addfindAndRemove方法操作引用计数2


1 In a quick search, however, I couldn't find an obvious open source implementation of a concurrent multimap. 1然而,在快速搜索中,我找不到并发多图的明显开源实现。 Note that you don't actually need a capital-M Multimap implementation with all its functionality - you really only need "some multiset" with the ability to add an element, iterate over available elements, and "remove" an element (ie, decrement its refcount in the set). 请注意,您实际上并不需要具有其所有功能的大写M Multimap实现 - 您实际上只需要“一些多重集”,能够添加元素,迭代可用元素,并“删除”元素(即,减少)它在集合中的引用)。

2 I'm actually glossing over a few details of the reference counted implementation since in this case there is a possible race between the thread that decrements the refcount to zero and any thread calling add for the same element which may increment the refcount above zero (but the entry has already been removed). 2我实际上掩盖了引用计数实现的一些细节,因为在这种情况下,在将refcount递减为零的线程与为同一元素调用add任何线程之间可能存在竞争,这可能会使refcount增加到零以上(但该条目已被删除)。 This can be avoided but I don't go into details yet since it isn't clear if you want to support duplicates. 这可以避免,但我还没有详细说明,因为不清楚是否要支持重复。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM