|
| 1 | +ConcurrentSkipListMap and ConcurrentSkipList — Detailed Explanation |
| 2 | + |
| 3 | +Quick summary |
| 4 | +• ConcurrentSkipListMap |
| 5 | +A concurrent, sorted map implementation in java.util.concurrent. It implements ConcurrentNavigableMap<K,V>. |
| 6 | +Internally it uses a skip-list to store key/value entries in sorted order. |
| 7 | +It is designed for high concurrency: reads are essentially non-blocking and many writes can proceed without global locks. |
| 8 | +Iterators are weakly consistent (they tolerate concurrent updates and do not throw ConcurrentModificationException). |
| 9 | + |
| 10 | +• ConcurrentSkipList (usually referring to ConcurrentSkipListSet) |
| 11 | +A concurrent, sorted set built on top of a ConcurrentSkipListMap (the set is essentially the map’s keys). |
| 12 | +In Java the class is ConcurrentSkipListSet<E>; it implements ConcurrentNavigableSet<E>. |
| 13 | +It provides the same concurrency and ordering properties as the map, but for unique keys only. |
| 14 | + |
| 15 | +⸻ |
| 16 | + |
| 17 | +Core idea: what is a skip list? |
| 18 | + |
| 19 | +A skip list is a layered linked-list structure that provides probabilistic balancing: |
| 20 | +• Level 0: a standard sorted singly linked list containing all nodes. |
| 21 | +• Level 1..L: higher levels contain a subset of nodes, allowing you to “skip” many items quickly. |
| 22 | +• Searching: start at the top level, move right until the next key would overshoot, |
| 23 | + then drop down one level and repeat until level 0. |
| 24 | +• Average-case performance: operations (search/insert/delete) are expected O(log n). |
| 25 | + The balancing comes from random or probabilistic promotion of nodes to higher levels. |
| 26 | + |
| 27 | +ASCII sketch (small): |
| 28 | + |
| 29 | +Level 3: HEAD -------------------------> [50] -----------------> null |
| 30 | +Level 2: HEAD --------------> [20] -> [50] -> [90] -> null |
| 31 | +Level 1: HEAD -----> [10] -> [20] -> [30] -> [50] -> null |
| 32 | +Level 0: HEAD -> [5] -> [10] -> [15] -> [20] -> [25] -> [30] -> [40] -> [50] -> null |
| 33 | + |
| 34 | + |
| 35 | +⸻ |
| 36 | + |
| 37 | +Why skip lists for concurrent sorted maps/sets? |
| 38 | +• Skip lists are simpler to make concurrent than balanced trees. Their updates are local (affect a few pointers) |
| 39 | + and can be implemented using CAS (compare-and-swap) on pointer fields. |
| 40 | +• They allow a high degree of concurrency because different threads often touch different parts of the structure. |
| 41 | +• Readers can traverse with minimal synchronization and remain safe (weakly consistent) while writers make changes. |
| 42 | + |
| 43 | +⸻ |
| 44 | + |
| 45 | +Internal structure (high level) |
| 46 | +• Node: contains key, value (map only), and an array or sequence of forward/next pointers—one per level that node appears in. |
| 47 | +• Head: a sentinel node with highest level pointers. |
| 48 | +• Level management: when inserting a node, you choose how many levels it participates in |
| 49 | + (traditionally by coin-flip probability or an equivalent RNG). Higher-level pointers skip farther. |
| 50 | +• Atomic updates: link/unlink operations use atomic CAS on next pointers. |
| 51 | + A removal may be done in two phases: logical removal (marking) then physical unlinking (CAS to remove). |
| 52 | +• Helping: concurrent algorithms often let other threads “help” finish a partially-completed update to avoid |
| 53 | + inconsistent state and reduce retries. |
| 54 | +• Weakly-consistent iterators: iteration walks next pointers and tolerates concurrently inserted/removed nodes. |
| 55 | + The iterator may or may not see every concurrent update. |
| 56 | + |
| 57 | +⸻ |
| 58 | + |
| 59 | +How operations work (conceptually) |
| 60 | + |
| 61 | +Search (get / contains): |
| 62 | +• Start at top level, move right while next key < target. When you cannot move right, drop down a level. |
| 63 | + Continue until level 0 and then inspect candidate node. |
| 64 | + |
| 65 | +Insert (put / add): |
| 66 | +1. Locate predecessor nodes at each level where new node will link in. |
| 67 | +2. Allocate new node and set its forward pointers to successors. |
| 68 | +3. Using CAS, splice the new node at each level, typically from lowest to highest or vice versa, with retries on CAS failure. |
| 69 | +4. If collisions or competing updates occur, retry the local CAS or help complete concurrent updates. |
| 70 | + |
| 71 | +Remove (delete / remove): |
| 72 | +1. Find node and mark it logically removed (often by setting a flag or special value in the next pointer). |
| 73 | +2. Physically unlink node by CAS on predecessor.next to bypass the removed node. |
| 74 | +3. Other threads may help unlink. |
| 75 | + |
| 76 | +Because operations touch a small neighbourhood of pointers, different keys can be manipulated concurrently with low contention. |
| 77 | + |
| 78 | +⸻ |
| 79 | + |
| 80 | +Concurrency model & guarantees |
| 81 | +• Thread-safety: built-in; no external synchronization needed. |
| 82 | +• Locking: mostly lock-free or fine-grained CAS; no single global lock. |
| 83 | +• Iterators: weakly consistent — they traverse elements reflecting some state between creation and end, but they do |
| 84 | + not throw ConcurrentModificationException. |
| 85 | +• Progress: operations make progress without blocking; writers may retry operations under contention but do not block |
| 86 | + other threads generally. |
| 87 | + |
| 88 | +⸻ |
| 89 | + |
| 90 | +Complexity |
| 91 | +• Average/expected time: O(log n) for get, put, remove, contains. |
| 92 | +• Memory: extra pointers per node (one per level) increase memory overhead compared to a plain linked list; |
| 93 | + expected levels per node is constant (geometric distribution). |
| 94 | +• Worst-case (degenerate) is O(n) but rare because of probabilistic balancing. |
| 95 | + |
| 96 | +⸻ |
| 97 | + |
| 98 | +Key differences: ConcurrentSkipListMap vs ConcurrentSkipListSet |
| 99 | +• ConcurrentSkipListMap<K,V> |
| 100 | +• Stores key→value entries; supports full Map and NavigableMap APIs. |
| 101 | +• Supports operations like put, get, remove, firstKey, subMap, ceilingKey, and other navigable methods. |
| 102 | +• Good when you need sorted associations (key → value). |
| 103 | +• ConcurrentSkipListSet |
| 104 | +• Backed by a ConcurrentSkipListMap<E, Boolean> or similar internal structure (the set is basically the map’s keys). |
| 105 | +• Provides set-style API (add, remove, contains, first, etc.). |
| 106 | +• Use when you only need a sorted collection of unique elements. |
| 107 | + |
| 108 | +⸻ |
| 109 | + |
| 110 | +Typical use cases |
| 111 | +• Concurrent ordered indexes (time-based events, scheduling). |
| 112 | +• Concurrent caches or registries where keys must be sorted or you need navigable operations (range queries). |
| 113 | +• Multi-threaded environments where you need non-blocking reads with frequent concurrent updates. |
| 114 | +• Implementing priority-like behavior when keys represent priorities or time-stamps. |
| 115 | + |
| 116 | +⸻ |
| 117 | + |
| 118 | +Comparisons with other concurrent maps |
| 119 | +• ConcurrentSkipListMap vs TreeMap |
| 120 | +• TreeMap is not thread-safe and is based on red-black trees. ConcurrentSkipListMap is thread-safe and better for |
| 121 | + concurrent access with similar O(log n) guarantees. |
| 122 | +• ConcurrentSkipListMap vs ConcurrentHashMap |
| 123 | +• ConcurrentHashMap is unordered and optimized for raw throughput (hash-based). |
| 124 | + If you do not need ordering, prefer ConcurrentHashMap for higher throughput. |
| 125 | + If you need ordering or navigable methods, use ConcurrentSkipListMap. |
| 126 | +• ConcurrentSkipListMap vs ConcurrentSkipListSet |
| 127 | +• Set is just the keys view; choose whichever matches your need (map for key→value, set for unique keys only). |
| 128 | + |
| 129 | +⸻ |
| 130 | + |
| 131 | +Practical considerations & tips |
| 132 | +• Avoid null keys and values: these implementations do not permit null keys or null values. |
| 133 | +• Iterators are good for monitoring and non-critical scans; they are not snapshots and may not reflect all updates. |
| 134 | +• If you need strict, consistent snapshots for range queries, consider copying data to a separate structure or using external synchronization. |
| 135 | +• Memory: be mindful of the per-node pointers — skip lists typically consume more memory than hash maps or single linked lists. |
| 136 | +• For ordered concurrent maps with navigational operations, ConcurrentSkipListMap is one of the best standard options. |
| 137 | + |
| 138 | +⸻ |
| 139 | + |
| 140 | +ASCII example of concurrent insertion (conceptual) |
| 141 | + |
| 142 | +Thread A inserts key 40: |
| 143 | + - Locates prev nodes at each needed level. |
| 144 | + - Attempts CAS on prev.level0.next from old -> new(40)->oldNext |
| 145 | + - If CAS succeeds at level0, proceeds to higher levels. |
| 146 | + - If level0 CAS loses (another thread inserted nearby), Thread A retries search and CAS. |
| 147 | + |
| 148 | +Thread B concurrently inserts key 35: |
| 149 | + - It operates in a nearby region but uses CAS on pointers for its own prev nodes. |
| 150 | + - Both can succeed as long as they update different next pointers or CAS ordering allows. |
| 151 | + |
| 152 | +⸻ |
| 153 | + |
| 154 | +Final summary |
| 155 | +• ConcurrentSkipListMap and ConcurrentSkipListSet are high-quality, concurrent, sorted collections built on skip lists. |
| 156 | +• They provide expected O(log n) performance, excellent concurrency (non-blocking reads, fine-grained updates), and navigable APIs. |
| 157 | +• Use them when you need sorted, thread-safe access with many concurrent readers/writers; |
| 158 | + use ConcurrentHashMap instead when ordering is not required and raw throughput is the highest priority. |
| 159 | + |
| 160 | +⸻ |
0 commit comments