0

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.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 1
    Can you clarify on _why_ you want to do this, please? It sounds like either a queue is not the appropriate data structure for you, or you're trying to work around something you shouldn't. Btw I'd vote for #1, and either fix equals(), or look for an IdentityQueue implementation (or write one). – Gergely Jan 30 '15 at 00:01
  • @sprinter: that is false. Look up the source code. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/LinkedList.java#LinkedList.remove%28java.lang.Object%29 That traverses the list, and uses `equals`, not `==`, to find the object to remove, so it doesn't necessarily remove the exact object referenced. – Louis Wasserman Jan 30 '15 at 00:15
  • @LouisWasserman thanks - I stand corrected and have deleted my comment. – sprinter Jan 30 '15 at 00:17
  • 2
    @Gergely - why I'd want to remove an element from a queue? Why I'd want to remove anything from a collection? I guess the reasons pretty much the span the gamut, but how about having a concurrent queue of jobs, and wanting to remove a job from the queue? – BeeOnRope Jan 30 '15 at 06:14
  • 1
    @BeeOnRope I think his question is more "Why are you using a Queue, rather than say a List or Map, when you need random access to objects". Data structures like Queues and Stacks are usually used for their intrinsic properties, rather than simply "Because they are collections". If performance is your main concern, choosing a data structure which more appropriately suits your needs like a `ConcurrentSkipListMap`, seems a better strategy. – Mikkel Løkke Jan 30 '15 at 06:53
  • Well I need fast concurrent insertion and deletion in order, and to be able to delete arbitrary elements. Which structure fits? The Queue itself *does* support random access deletion, as shown in the snipped above. I just can't access it (without reflection tricks, say). So it's not a matter of the wrong tool for the job. – BeeOnRope Jan 30 '15 at 08:20

2 Answers2

0

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.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • It would definitely not be useful for `ArrayList` and shouldn't be offered there. I never suggested it should be part of `List` or `Collection` at all. It should be part of `Queue` and `LinkedList` though. – BeeOnRope Jan 30 '15 at 06:17
  • I think it's a core operation from a performance point of view, since there is _no other way_ to efficiently to do the same thing, and being able to remove elements from an arbitrary position is pretty much the only reason to use a linked list over an array list: pretty much the first thing you learn when those structures were introduced in Algorithms 101 or whatever. – BeeOnRope Jan 30 '15 at 06:20
  • I guess it can't be in `Queue` since that also has array-based implementations, but it should be offered as an additional method on the node-based structures, particularly the concurrent queue based ones. The frustrating thing is that you *can* get this functionality to some extent with iterators, but in the concurrent case getting the iterator you want is messy. – BeeOnRope Jan 30 '15 at 06:26
0

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.

sprinter
  • 27,148
  • 6
  • 47
  • 78
  • Well it doesn't take massive collections to make this a problem. Even pretty pedestrian collection of 10,000 elements is going to take a while to remove. It means that if you remove most (or any fixed fraction of) elements eventually, you paid an N^2 price. – BeeOnRope Jan 30 '15 at 06:17
  • @BeeOnRope that's why linked lists are not a good choice for anything other than short lists. – sprinter Jan 30 '15 at 06:22
  • Exactly. I'm not using `LinkedList` though (I threw it in as an example of a structure whose key benefit can't be used due to this issue). I'm using ConcurrentLinkedQueue. There is nothing close to that in the JDK that I'm aware of that can do what I want. – BeeOnRope Jan 30 '15 at 06:27
  • I've dealt with massive collections before but I've never come across this problem because in all cases I've found there to be some attribute of the collection elements that can naturally be used for equality and hashing. HashMaps and equivalent are very efficient for large collections. I've never come across the situation where there was a need to remove the exact object you had added. But if you need it then the suggestion in my answer is to write your own. There's actually not a lot of source code in most of the collection implementations. – sprinter Jan 30 '15 at 06:34
  • Yes, it doesn't come up much because `HashMap` is the dominant structure in most Java code. See the start of my question though: I need *Concurrent* `LinkedQueue`. There is nothing like that in the map word in Java: 1) unlocked (concurrent) insertion 2) maintain order of insertion – BeeOnRope Jan 30 '15 at 06:52
  • @BeeOnRope fair point. I guess you'll need to write your own. Good luck. – sprinter Jan 30 '15 at 08:17