|
| 1 | +ConcurrentSkipListMap — Internal working: |
| 2 | + |
| 3 | +What it is (high level) |
| 4 | + |
| 5 | +ConcurrentSkipListMap is a concurrent, sorted map implementation in java.util.concurrent. |
| 6 | +It implements ConcurrentNavigableMap and stores entries in sorted order (natural ordering or via a Comparator). |
| 7 | + |
| 8 | +Internally it uses a skip list data structure, which provides expected O(log n) time for get, put, remove, and |
| 9 | +navigational operations. |
| 10 | + |
| 11 | +It is designed for highly concurrent access without global locks — reads are essentially lock-free and many writes |
| 12 | +proceed concurrently. |
| 13 | + |
| 14 | +⸻ |
| 15 | + |
| 16 | +When to use it (use-cases) |
| 17 | +• You need a thread-safe, sorted map with non-blocking or low-lock concurrency behavior. |
| 18 | +• Useful for concurrent ordered-index scenarios: scheduling, time-indexed events, priority tables, caches that require sorted keys. |
| 19 | +• Prefer ConcurrentSkipListMap over TreeMap when concurrency is required (TreeMap is not thread-safe). |
| 20 | + |
| 21 | +⸻ |
| 22 | + |
| 23 | +Key properties |
| 24 | +• Thread-safe and highly concurrent. |
| 25 | +• Sorted order guaranteed (Comparator or natural order). |
| 26 | +• Non-blocking reads (lock-free or minimal internal synchronization). |
| 27 | +• Expected O(log n) for most operations. |
| 28 | +• Iterators are weakly consistent: they reflect some, but not necessarily all, |
| 29 | +updates since the iterator was created and they do not throw ConcurrentModificationException. |
| 30 | +• No null keys or null values allowed. |
| 31 | + |
| 32 | +⸻ |
| 33 | + |
| 34 | +How a skip list works (brief theory) |
| 35 | + |
| 36 | +A skip list stores the same elements in multiple levels of linked lists. |
| 37 | +Level 0 is the base sorted linked list (all elements). |
| 38 | +Higher levels contain increasingly sparse subsets of elements; |
| 39 | +each higher level lets you “skip” across many nodes. |
| 40 | + |
| 41 | +To search, you start at the top level and move right as long as the next key is less than the target; |
| 42 | +when you can’t move right, drop down one level and repeat. This gives average O(log n) search time. |
| 43 | + |
| 44 | +In ConcurrentSkipListMap the nodes and links are implemented with CAS-friendly fields so multiple threads can |
| 45 | +update different parts concurrently. |
| 46 | + |
| 47 | +⸻ |
| 48 | + |
| 49 | +ASCII diagram — skip list structure (simple) |
| 50 | + |
| 51 | +Level 3: HEAD -----------------------> [ 50 ] -----------------> [ 90 ] -> null |
| 52 | + skip many skip many |
| 53 | + |
| 54 | +Level 2: HEAD ---------------> [ 20 ] ----------> [ 50 ] ---> [ 90 ] -> null |
| 55 | + |
| 56 | +Level 1: HEAD ---------> [ 10 ] ---> [ 20 ] ---> [ 30 ] ---> [ 50 ] ---> [ 70 ] ---> null |
| 57 | + |
| 58 | +Level 0: HEAD -> [ 5 ] -> [10] -> [15] -> [20] -> [25] -> [30] -> [40] -> [50] -> [70] -> [90] -> null |
| 59 | + |
| 60 | +Search for 40: |
| 61 | +• Start at Level 3 HEAD: move right while next < 40, then drop levels until Level 0, then traverse to exact node. |
| 62 | + |
| 63 | +Insert 40: |
| 64 | +• Determine levels for new node (randomized in classic skip list; in concurrent implementation, there are probabilistic choices). |
| 65 | +• CAS link updates are used to splice new node into lists at each level. |
| 66 | + |
| 67 | +⸻ |
| 68 | + |
| 69 | +Concurrent aspects (how concurrency is handled) |
| 70 | +• ConcurrentSkipListMap uses non-blocking algorithms and CAS on node fields to update links. It avoids coarse-grained locks. |
| 71 | +• Readers are not blocked by writers: traversals can proceed while updates happen, and iterators are weakly consistent. |
| 72 | +• Structural updates use careful CAS loops and “helping” strategies (threads may help finish partially-completed updates) to maintain correctness. |
| 73 | +• The data structure tolerates concurrent puts/removes with minimal contention because operations affect a small local portion of the list (near the key’s neighbors). |
| 74 | + |
| 75 | +⸻ |
| 76 | + |
| 77 | +Complexity summary |
| 78 | +• get(K key): expected O(log n) (search down levels). |
| 79 | +• put(K key, V value): expected O(log n) (find position + link insert). |
| 80 | +• remove(K key): expected O(log n) (find + CAS unlink). |
| 81 | +• firstKey()/lastKey(): O(log n) to locate ends. |
| 82 | +• subMap, headMap, tailMap: return views backed by the map; operations are O(log n) per access. |
| 83 | + |
| 84 | +⸻ |
| 85 | + |
| 86 | +Important characteristics & semantics |
| 87 | +• Sorted order maintained at all times. |
| 88 | +• Weakly-consistent iterators: they reflect some snapshot but allow concurrent modification. |
| 89 | +• No null keys/values: attempts will throw NullPointerException. |
| 90 | +• Thread-safety guaranteed by internal CAS and fine-grained coordination; external synchronization not required. |
| 91 | + |
| 92 | +⸻ |
| 93 | + |
| 94 | +Main API highlights (useful methods) |
| 95 | +• V put(K key, V value) |
| 96 | +• V get(Object key) |
| 97 | +• V remove(Object key) |
| 98 | +• K firstKey(), K lastKey() |
| 99 | +• NavigableSet<K> keySet(), NavigableMap<K,V> subMap(K fromKey, K toKey, boolean fromInclusive, boolean toInclusive) |
| 100 | + |
| 101 | +⸻ |
| 102 | + |
| 103 | +Notes on the example |
| 104 | +• Writers run concurrently and insert keys. |
| 105 | +• Iteration during writes is allowed — the iterator is weakly consistent (it won’t throw ConcurrentModificationException). |
| 106 | +• Final map content is in sorted order. |
| 107 | + |
| 108 | +⸻ |
| 109 | + |
| 110 | +Internals — more detailed (implementation notes) |
| 111 | +• Node structure: nodes contain key, value, and arrays or links to next nodes at different levels. |
| 112 | +• CAS on node next pointers is used to insert/delete safely; some algorithms use marked flags to indicate logically removed nodes before physical unlinking. |
| 113 | +• The map uses probabilistic level assignment (or similar heuristics) to decide how many levels a new node spans — this produces balanced performance on average. |
| 114 | +• Unlike balanced trees (like red-black trees), skip lists trade a small constant factor in performance for simpler, highly concurrent update algorithms. |
| 115 | + |
| 116 | +⸻ |
| 117 | + |
| 118 | +Comparisons |
| 119 | +• ConcurrentSkipListMap vs TreeMap: TreeMap is balanced tree (Red-Black), not thread-safe; ConcurrentSkipListMap is concurrent and lock-free-friendly with similar O(log n) guarantees but better for concurrent use. |
| 120 | +• ConcurrentSkipListMap vs ConcurrentHashMap: ConcurrentHashMap is for unordered maps with better throughput for pure key-based lookup; ConcurrentSkipListMap provides ordering and navigable operations at cost of slightly higher per-op overhead. |
| 121 | +• ConcurrentSkipListMap vs ConcurrentSkipListSet: the set is a thin wrapper over ConcurrentSkipListMap keys. |
| 122 | + |
| 123 | +⸻ |
| 124 | + |
| 125 | +Pros and cons |
| 126 | + |
| 127 | +Pros: |
| 128 | +• Thread-safe sorted map with high concurrency. |
| 129 | +• Navigable operations available (lowerKey, higherKey, ceiling, floor, etc.). |
| 130 | +• Iterators are safe for concurrent use (weakly consistent). |
| 131 | + |
| 132 | +Cons: |
| 133 | +• Higher memory overhead (multiple levels, extra pointers). |
| 134 | +• Slightly slower per-op constants than highly-optimized unordered concurrent maps like ConcurrentHashMap. |
| 135 | +• Randomized structure — guarantees are probabilistic (but reliable in practice). |
| 136 | + |
| 137 | +⸻ |
| 138 | + |
| 139 | +ASCII flow for insertion (concurrent-friendly view) |
| 140 | + |
| 141 | +1) Find insertion point starting from the top level |
| 142 | + - traverse right while next.key < key |
| 143 | + - when can't move right, move down |
| 144 | + |
| 145 | +2) Once position at each level decided, attempt CAS on next at that level: |
| 146 | + oldNext = prev.next[level] |
| 147 | + if CAS(prev.next[level], oldNext, newNode with next=oldNext) succeeds -> level linked |
| 148 | + else -> retry (helping or re-search) |
| 149 | + |
| 150 | +3) After linking all required levels, insertion is visible to readers. |
| 151 | + |
| 152 | +4) If concurrent removals occur, removal marks nodes and then unlinks them with CAS. |
| 153 | + |
| 154 | + |
| 155 | +⸻ |
| 156 | + |
| 157 | +Practical tips |
| 158 | +• Because iterators are weakly consistent, they’re excellent for diagnostics and non-critical scans — they give a snapshot-ish view without blocking writers. |
| 159 | +• Use when both ordering and concurrency are important (e.g., time-ordered event processing). |
| 160 | +• Avoid if you need strict snapshot semantics — use external synchronization or copy-on-write patterns. |
| 161 | + |
| 162 | +⸻ |
0 commit comments