问题
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:
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.
Use
ConcurrentLinkedDeque
instead, thenaddLast()
my element, then immediately grab adescendingIterator()
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 outIterator.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