Efficiently removing an element added to a ConcurrentQueue

荒凉一梦 提交于 2019-12-12 03:22:07

问题


In principle it is easy to remove an element from ConcurrentLinkedQueue or similar implementation. For example, the Iterator for that class supports efficient O(1) removal of the current element:

public void remove() {
    Node<E> l = lastRet;
    if (l == null) throw new IllegalStateException();
    // rely on a future traversal to relink.
    l.item = null;
    lastRet = null;
}

I want to add an element to the queue with add(), and then later delete that exact element from the queue. The only options I can see are:

  1. Save a reference to the object and call ConcurrentLinkedQueue.remove(Object o) with the object - but this forces a traversal of the whole queue in the worst case (and half on average with a random add and removal pattern).

    This has the further issue that it doesn't necessarily remove the same object I inserted. It removes an equal object, which may very be a different one if multiple objects in my queue are equal.

  2. Use ConcurrentLinkedDeque instead, then addLast() my element, then immediately grab a descendingIterator() and iterate until I find my element (which will often be the first one, but may be later since I'm effectively swimming against the tide of concurrent additions).

    This addition to being awkward and potentially quite slow, this forces me to use Deque class which in this case is much more complex and slower for many operations (check out Iterator.remove() for that class!).

    Furthermore this solution still has a subtle failure mode if identical (i.e., == identity) can be inserted, because I might find the object inserted by someone else, but that can ignored in the usual case that is not possible.

Both solutions seem really awkward, but deleting an arbitrary element in these kind of structures seems like a core operation. What am I missing?

It occurs to me this is a general issue with other concurrent lists and dequeues and even with non concurrent structures like LinkedList.

C++ offers it in the form of methods like insert.


回答1:


Nope, there's not really any way of doing this in the Java APIs; it's not really considered a core operation.

For what it's worth, there are some significant technical difficulties to how you would do it in the first place. For example, consider ArrayList. If adding an element to an ArrayList gave you another reference object that told you where that element was...

  • you'd be adding an allocation to every add operation
  • each object would have to keep track of one (or more!) references to its "pointer" object, which would at least double the memory consumption of the data structure
  • every time you inserted an element into the ArrayList, you'd have to update the pointer objects for every other element whose position shifted

In short, while this might be a reasonable operation for linked-list-based data structures, there's not really any good way of fitting it into a more general API, so it's pretty much omitted.




回答2:


If you specifically need this capability (e.g. you are going to have massive collections) then you will likely need to implement your own collection that returns a reference to the entry on add and has the ability to remove in O(1).

class MyLinkedList<V> {
    public class Entry {
        private Entry next;
        private Entry prev;
        private V value;
    }

    public Entry add(V value) {
        ...
    }

    public void remove(Entry entry) {
        ...
    }
}

In other words you are not removing by value but by reference to the collection entry:

MyLinkedList<Integer> intList;
MyLinkedList.Entry entry = intList.add(15);
intList.remove(entry);

That's obviously a fair amount of work to implement.



来源:https://stackoverflow.com/questions/28226759/efficiently-removing-an-element-added-to-a-concurrentqueue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!