A concurrent AVL tree with fast cloning, based on the algorithm of Bronson, Casper, Chafi, and Olukotun, "A Practical Concurrent Binary Search Tree" published in PPoPP'10. To simplify the locking protocols rebalancing work is performed in pieces, and some removed keys are be retained as routing nodes in the tree.
This data structure honors all of the contracts of {@link java.util.concurrent.ConcurrentSkipListMap}, with the additional contract that clone, size, toArray, and iteration are linearizable (atomic).
The tree uses optimistic concurrency control. No locks are usually required for get, containsKey, firstKey, firstEntry, lastKey, or lastEntry. Reads are not lock free (or even obstruction free), but obstructing threads perform no memory allocation, system calls, or loops, which seems to work okay in practice. All of the updates to the tree are performed in fixed- size blocks, so restoration of the AVL balance criteria may occur after a change to the tree has linearized (but before the mutating operation has returned). The tree is always properly balanced when quiescent.
To clone the tree (or produce a snapshot for consistent iteration) the root node is marked as shared, which must be (*) done while there are no pending mutations. New mutating operations are blocked if a mark is pending, and when existing mutating operations are completed the mark is made. * - It would be less disruptive if we immediately marked the root as shared, and then waited for pending operations that might not have seen the mark without blocking new mutations. This could result in imbalance being frozen into the shared portion of the tree, though. To minimize the problem we perform the mark and reenable mutation on whichever thread notices that the entry count has become zero, to reduce context switches on the critical path.
The same multi-cache line data structure required for efficiently tracking the entry and exit for mutating operations is used to maintain the current size of the tree. This means that the size can be computed by quiescing as for a clone, but without doing any marking.
Range queries such as higherKey are not amenable to the optimistic hand-over-hand locking scheme used for exact searches, so they are implemented with pessimistic concurrency control. Mutation can be considered to acquire a lock on the map in Intention-eXclusive mode, range queries, size(), and root marking acquire the lock in Shared mode.
@author Nathan Bronson