Hey Christian,
I'm still working out the exact details, but I know that I am very
close. The buffer strategy brought me 95% there with just the exact
details needing to be hashed out. There's an implicit assumption
that's not shown of using per-node locks (volatile boolean flags) to
avoid race conditions with sequential elements. This is needed since
removals do need to touch other elements, but the main advantage of
the buffer is to allow two locks - not three. Using one less lock
makes the algorithm far simpler and less error prone than attempting a
3 node lock, since that opens a big can of worms...
Last night I began working on the exact details. I took a step back
and instead of immediately going with the doubly-linked list, I
started with a singly-linked using buffers. In this case, the map
points to the buffer, which points to the element. We can drop the
element, but have the extra buffer. However, we can't remove that
extra buffer cleanly because another key points to it. We can't drop
the buffer for the element we removed because we don't know who is
pointing to it. For the buffer we wish to remove, we could
potentially get its key (either on the buffer or its element,
whichever is easier) and perform a conditional replacement in the
map. That should work, but requires another write operation to the
map. Since the JDK's ConcurrentHashMap is lock-on-write, its not
ideal. If it was concurrently removed, we could probably just drop
that buffer since its no longer being pointed to. I think this would
work, but its not perfect performance-wise.
To solve this, I augmented the above to put a back pointer on the
element. This seems to work out perfectly and I could easily write a
lock-free algorithm. The head/tail are replaced with a single
sentinel node with a buffer (making the initial state look quite
funky!). This gives the O(1) insertion/removal properties of a doubly-
linked list, but loses its reverse iterator ability. Since iterating
isn't necessary (we return the ConcurrentMap's iterator, as no value
in ordering), this is acceptable. I am tentatively calling this a
"3/2 linked list".
Tonight or tomorrow I will try to work out the exact details for the
doubly-linked list described in the wiki. I think this is possible,
but it would require a few extra steps due to the additional pointer.
Since the 3/2 list solves our needs, this extra complexity only adds
performance overhead. Of course, if we wanted to offer ordered
traversal (as in fifo/lru, not sorted) then reverse ordering could be
desirable. I can't see the benefit, though.
I'll probably document all three approaches since it helps one grok
the details better. The 3/2-linked list looks to be a really unique
idea, which is pretty cool. Once I have the entire design fully
documented I'll begin writing it. I'm afraid to start too early
because there are enough race conditions that I can't just start
banging it out!
An article would fun. I'll consider it once the implementation is
done and I get some feedback. :-)
Best,
Ben
> ...
>
> read more »