diff options
Diffstat (limited to 'luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java')
-rw-r--r-- | luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java | 1149 |
1 files changed, 671 insertions, 478 deletions
diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java index c142e2a..c85a5cc 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java @@ -1,16 +1,13 @@ /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain + * http://creativecommons.org/publicdomain/zero/1.0/ */ package java.util.concurrent; import java.util.concurrent.locks.*; import java.util.*; import java.io.Serializable; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; // BEGIN android-note // removed link to collections framework docs @@ -76,7 +73,25 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> /* * The basic strategy is to subdivide the table among Segments, - * each of which itself is a concurrently readable hash table. + * each of which itself is a concurrently readable hash table. To + * reduce footprint, all but one segments are constructed only + * when first needed (see ensureSegment). To maintain visibility + * in the presence of lazy construction, accesses to segments as + * well as elements of segment's table must use volatile access, + * which is done via Unsafe within methods segmentAt etc + * below. These provide the functionality of AtomicReferenceArrays + * but reduce the levels of indirection. Additionally, + * volatile-writes of table elements and entry "next" fields + * within locked operations use the cheaper "lazySet" forms of + * writes (via putOrderedObject) because these writes are always + * followed by lock releases that maintain sequential consistency + * of table updates. + * + * Historical note: The previous version of this class relied + * heavily on "final" fields, which avoided some volatile reads at + * the expense of a large initial footprint. Some remnants of + * that design (including forced construction of segment 0) exist + * to ensure serialization compatibility. */ /* ---------------- Constants -------------- */ @@ -108,8 +123,15 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> static final int MAXIMUM_CAPACITY = 1 << 30; /** + * The minimum capacity for per-segment tables. Must be a power + * of two, at least two to avoid immediate resizing on next use + * after lazy construction. + */ + static final int MIN_SEGMENT_TABLE_CAPACITY = 2; + + /** * The maximum number of segments to allow; used to bound - * constructor arguments. + * constructor arguments. Must be power of two less than 1 << 24. */ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative @@ -135,7 +157,7 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> final int segmentShift; /** - * The segments, each of which is a specialized hash table + * The segments, each of which is a specialized hash table. */ final Segment<K,V>[] segments; @@ -143,7 +165,66 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> transient Set<Map.Entry<K,V>> entrySet; transient Collection<V> values; - /* ---------------- Small Utilities -------------- */ + /** + * ConcurrentHashMap list entry. Note that this is never exported + * out as a user-visible Map.Entry. + */ + static final class HashEntry<K,V> { + final int hash; + final K key; + volatile V value; + volatile HashEntry<K,V> next; + + HashEntry(int hash, K key, V value, HashEntry<K,V> next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + /** + * Sets next field with volatile write semantics. (See above + * about use of putOrderedObject.) + */ + final void setNext(HashEntry<K,V> n) { + UNSAFE.putOrderedObject(this, nextOffset, n); + } + + // Unsafe mechanics + static final sun.misc.Unsafe UNSAFE; + static final long nextOffset; + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class<?> k = HashEntry.class; + nextOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("next")); + } catch (Exception e) { + throw new Error(e); + } + } + } + + /** + * Gets the ith element of given table (if nonnull) with volatile + * read semantics. Note: This is manually integrated into a few + * performance-sensitive methods to reduce call overhead. + */ + @SuppressWarnings("unchecked") + static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) { + return (tab == null) ? null : + (HashEntry<K,V>) UNSAFE.getObjectVolatile + (tab, ((long)i << TSHIFT) + TBASE); + } + + /** + * Sets the ith element of given table, with volatile write + * semantics. (See above about use of putOrderedObject.) + */ + static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i, + HashEntry<K,V> e) { + UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e); + } /** * Applies a supplemental hash function to a given hashCode, which @@ -164,104 +245,67 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> } /** - * Returns the segment that should be used for key with given hash - * @param hash the hash code for the key - * @return the segment - */ - final Segment<K,V> segmentFor(int hash) { - return segments[(hash >>> segmentShift) & segmentMask]; - } - - /* ---------------- Inner Classes -------------- */ - - /** - * ConcurrentHashMap list entry. Note that this is never exported - * out as a user-visible Map.Entry. - * - * Because the value field is volatile, not final, it is legal wrt - * the Java Memory Model for an unsynchronized reader to see null - * instead of initial value when read via a data race. Although a - * reordering leading to this is not likely to ever actually - * occur, the Segment.readValueUnderLock method is used as a - * backup in case a null (pre-initialized) value is ever seen in - * an unsynchronized access method. - */ - static final class HashEntry<K,V> { - final K key; - final int hash; - volatile V value; - final HashEntry<K,V> next; - - HashEntry(K key, int hash, HashEntry<K,V> next, V value) { - this.key = key; - this.hash = hash; - this.next = next; - this.value = value; - } - - @SuppressWarnings("unchecked") - static final <K,V> HashEntry<K,V>[] newArray(int i) { - return new HashEntry[i]; - } - } - - /** * Segments are specialized versions of hash tables. This * subclasses from ReentrantLock opportunistically, just to * simplify some locking and avoid separate construction. */ static final class Segment<K,V> extends ReentrantLock implements Serializable { /* - * Segments maintain a table of entry lists that are ALWAYS - * kept in a consistent state, so can be read without locking. - * Next fields of nodes are immutable (final). All list - * additions are performed at the front of each bin. This - * makes it easy to check changes, and also fast to traverse. - * When nodes would otherwise be changed, new nodes are - * created to replace them. This works well for hash tables - * since the bin lists tend to be short. (The average length - * is less than two for the default load factor threshold.) - * - * Read operations can thus proceed without locking, but rely - * on selected uses of volatiles to ensure that completed - * write operations performed by other threads are - * noticed. For most purposes, the "count" field, tracking the - * number of elements, serves as that volatile variable - * ensuring visibility. This is convenient because this field - * needs to be read in many read operations anyway: - * - * - All (unsynchronized) read operations must first read the - * "count" field, and should not look at table entries if - * it is 0. + * Segments maintain a table of entry lists that are always + * kept in a consistent state, so can be read (via volatile + * reads of segments and tables) without locking. This + * requires replicating nodes when necessary during table + * resizing, so the old lists can be traversed by readers + * still using old version of table. * - * - All (synchronized) write operations should write to - * the "count" field after structurally changing any bin. - * The operations must not take any action that could even - * momentarily cause a concurrent read operation to see - * inconsistent data. This is made easier by the nature of - * the read operations in Map. For example, no operation - * can reveal that the table has grown but the threshold - * has not yet been updated, so there are no atomicity - * requirements for this with respect to reads. - * - * As a guide, all critical volatile reads and writes to the - * count field are marked in code comments. + * This class defines only mutative methods requiring locking. + * Except as noted, the methods of this class perform the + * per-segment versions of ConcurrentHashMap methods. (Other + * methods are integrated directly into ConcurrentHashMap + * methods.) These mutative methods use a form of controlled + * spinning on contention via methods scanAndLock and + * scanAndLockForPut. These intersperse tryLocks with + * traversals to locate nodes. The main benefit is to absorb + * cache misses (which are very common for hash tables) while + * obtaining locks so that traversal is faster once + * acquired. We do not actually use the found nodes since they + * must be re-acquired under lock anyway to ensure sequential + * consistency of updates (and in any case may be undetectably + * stale), but they will normally be much faster to re-locate. + * Also, scanAndLockForPut speculatively creates a fresh node + * to use in put if no node is found. */ private static final long serialVersionUID = 2249069246763182397L; /** - * The number of elements in this segment's region. + * The maximum number of times to tryLock in a prescan before + * possibly blocking on acquire in preparation for a locked + * segment operation. On multiprocessors, using a bounded + * number of retries maintains cache acquired while locating + * nodes. */ - transient volatile int count; + static final int MAX_SCAN_RETRIES = + Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; + + /** + * The per-segment table. Elements are accessed via + * entryAt/setEntryAt providing volatile semantics. + */ + transient volatile HashEntry<K,V>[] table; /** - * Number of updates that alter the size of the table. This is - * used during bulk-read methods to make sure they see a - * consistent snapshot: If modCounts change during a traversal - * of segments computing size or checking containsValue, then - * we might have an inconsistent view of state so (usually) - * must retry. + * The number of elements. Accessed only either within locks + * or among other volatile reads that maintain visibility. + */ + transient int count; + + /** + * The total number of mutative operations in this segment. + * Even though this may overflows 32 bits, it provides + * sufficient accuracy for stability checks in CHM isEmpty() + * and size() methods. Accessed only either within locks or + * among other volatile reads that maintain visibility. */ transient int modCount; @@ -273,11 +317,6 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> transient int threshold; /** - * The per-segment table. - */ - transient volatile HashEntry<K,V>[] table; - - /** * The load factor for the hash table. Even though this value * is same for all segments, it is replicated to avoid needing * links to outer object. @@ -285,202 +324,93 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> */ final float loadFactor; - Segment(int initialCapacity, float lf) { - loadFactor = lf; - setTable(HashEntry.<K,V>newArray(initialCapacity)); - } - - @SuppressWarnings("unchecked") - static final <K,V> Segment<K,V>[] newArray(int i) { - return new Segment[i]; - } - - /** - * Sets table to new HashEntry array. - * Call only while holding lock or in constructor. - */ - void setTable(HashEntry<K,V>[] newTable) { - threshold = (int)(newTable.length * loadFactor); - table = newTable; - } - - /** - * Returns properly casted first entry of bin for given hash. - */ - HashEntry<K,V> getFirst(int hash) { - HashEntry<K,V>[] tab = table; - return tab[hash & (tab.length - 1)]; + Segment(float lf, int threshold, HashEntry<K,V>[] tab) { + this.loadFactor = lf; + this.threshold = threshold; + this.table = tab; } - /** - * Reads value field of an entry under lock. Called if value - * field ever appears to be null. This is possible only if a - * compiler happens to reorder a HashEntry initialization with - * its table assignment, which is legal under memory model - * but is not known to ever occur. - */ - V readValueUnderLock(HashEntry<K,V> e) { - lock(); + final V put(K key, int hash, V value, boolean onlyIfAbsent) { + HashEntry<K,V> node = tryLock() ? null : + scanAndLockForPut(key, hash, value); + V oldValue; try { - return e.value; - } finally { - unlock(); - } - } - - /* Specialized implementations of map methods */ - - V get(Object key, int hash) { - if (count != 0) { // read-volatile - HashEntry<K,V> e = getFirst(hash); - while (e != null) { - if (e.hash == hash && key.equals(e.key)) { - V v = e.value; - if (v != null) - return v; - return readValueUnderLock(e); // recheck - } - e = e.next; - } - } - return null; - } - - boolean containsKey(Object key, int hash) { - if (count != 0) { // read-volatile - HashEntry<K,V> e = getFirst(hash); - while (e != null) { - if (e.hash == hash && key.equals(e.key)) - return true; - e = e.next; - } - } - return false; - } - - boolean containsValue(Object value) { - if (count != 0) { // read-volatile HashEntry<K,V>[] tab = table; - int len = tab.length; - for (int i = 0 ; i < len; i++) { - for (HashEntry<K,V> e = tab[i]; e != null; e = e.next) { - V v = e.value; - if (v == null) // recheck - v = readValueUnderLock(e); - if (value.equals(v)) - return true; + int index = (tab.length - 1) & hash; + HashEntry<K,V> first = entryAt(tab, index); + for (HashEntry<K,V> e = first;;) { + if (e != null) { + K k; + if ((k = e.key) == key || + (e.hash == hash && key.equals(k))) { + oldValue = e.value; + if (!onlyIfAbsent) { + e.value = value; + ++modCount; + } + break; + } + e = e.next; + } + else { + if (node != null) + node.setNext(first); + else + node = new HashEntry<K,V>(hash, key, value, first); + int c = count + 1; + if (c > threshold && tab.length < MAXIMUM_CAPACITY) + rehash(node); + else + setEntryAt(tab, index, node); + ++modCount; + count = c; + oldValue = null; + break; } } - } - return false; - } - - boolean replace(K key, int hash, V oldValue, V newValue) { - lock(); - try { - HashEntry<K,V> e = getFirst(hash); - while (e != null && (e.hash != hash || !key.equals(e.key))) - e = e.next; - - boolean replaced = false; - if (e != null && oldValue.equals(e.value)) { - replaced = true; - e.value = newValue; - } - return replaced; - } finally { - unlock(); - } - } - - V replace(K key, int hash, V newValue) { - lock(); - try { - HashEntry<K,V> e = getFirst(hash); - while (e != null && (e.hash != hash || !key.equals(e.key))) - e = e.next; - - V oldValue = null; - if (e != null) { - oldValue = e.value; - e.value = newValue; - } - return oldValue; - } finally { - unlock(); - } - } - - - V put(K key, int hash, V value, boolean onlyIfAbsent) { - lock(); - try { - int c = count; - if (c++ > threshold) // ensure capacity - rehash(); - HashEntry<K,V>[] tab = table; - int index = hash & (tab.length - 1); - HashEntry<K,V> first = tab[index]; - HashEntry<K,V> e = first; - while (e != null && (e.hash != hash || !key.equals(e.key))) - e = e.next; - - V oldValue; - if (e != null) { - oldValue = e.value; - if (!onlyIfAbsent) - e.value = value; - } - else { - oldValue = null; - ++modCount; - tab[index] = new HashEntry<K,V>(key, hash, first, value); - count = c; // write-volatile - } - return oldValue; } finally { unlock(); } + return oldValue; } - void rehash() { - HashEntry<K,V>[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) - return; - + /** + * Doubles size of table and repacks entries, also adding the + * given node to new table + */ + @SuppressWarnings("unchecked") + private void rehash(HashEntry<K,V> node) { /* - * Reclassify nodes in each list to new Map. Because we are - * using power-of-two expansion, the elements from each bin - * must either stay at same index, or move with a power of two - * offset. We eliminate unnecessary node creation by catching - * cases where old nodes can be reused because their next - * fields won't change. Statistically, at the default - * threshold, only about one-sixth of them need cloning when - * a table doubles. The nodes they replace will be garbage - * collectable as soon as they are no longer referenced by any - * reader thread that may be in the midst of traversing table - * right now. + * Reclassify nodes in each list to new table. Because we + * are using power-of-two expansion, the elements from + * each bin must either stay at same index, or move with a + * power of two offset. We eliminate unnecessary node + * creation by catching cases where old nodes can be + * reused because their next fields won't change. + * Statistically, at the default threshold, only about + * one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage + * collectable as soon as they are no longer referenced by + * any reader thread that may be in the midst of + * concurrently traversing table. Entry accesses use plain + * array indexing because they are followed by volatile + * table write. */ - - HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1); - threshold = (int)(newTable.length * loadFactor); - int sizeMask = newTable.length - 1; + HashEntry<K,V>[] oldTable = table; + int oldCapacity = oldTable.length; + int newCapacity = oldCapacity << 1; + threshold = (int)(newCapacity * loadFactor); + HashEntry<K,V>[] newTable = + (HashEntry<K,V>[]) new HashEntry<?,?>[newCapacity]; + int sizeMask = newCapacity - 1; for (int i = 0; i < oldCapacity ; i++) { - // We need to guarantee that any existing reads of old Map can - // proceed. So we cannot yet null out each bin. HashEntry<K,V> e = oldTable[i]; - if (e != null) { HashEntry<K,V> next = e.next; int idx = e.hash & sizeMask; - - // Single node on list - if (next == null) + if (next == null) // Single node on list newTable[idx] = e; - - else { - // Reuse trailing consecutive sequence at same slot + else { // Reuse consecutive sequence at same slot HashEntry<K,V> lastRun = e; int lastIdx = idx; for (HashEntry<K,V> last = next; @@ -493,74 +423,263 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> } } newTable[lastIdx] = lastRun; - - // Clone all remaining nodes + // Clone remaining nodes for (HashEntry<K,V> p = e; p != lastRun; p = p.next) { - int k = p.hash & sizeMask; + V v = p.value; + int h = p.hash; + int k = h & sizeMask; HashEntry<K,V> n = newTable[k]; - newTable[k] = new HashEntry<K,V>(p.key, p.hash, - n, p.value); + newTable[k] = new HashEntry<K,V>(h, p.key, v, n); } } } } + int nodeIndex = node.hash & sizeMask; // add the new node + node.setNext(newTable[nodeIndex]); + newTable[nodeIndex] = node; table = newTable; } /** + * Scans for a node containing given key while trying to + * acquire lock, creating and returning one if not found. Upon + * return, guarantees that lock is held. Unlike in most + * methods, calls to method equals are not screened: Since + * traversal speed doesn't matter, we might as well help warm + * up the associated code and accesses as well. + * + * @return a new node if key not found, else null + */ + private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { + HashEntry<K,V> first = entryForHash(this, hash); + HashEntry<K,V> e = first; + HashEntry<K,V> node = null; + int retries = -1; // negative while locating node + while (!tryLock()) { + HashEntry<K,V> f; // to recheck first below + if (retries < 0) { + if (e == null) { + if (node == null) // speculatively create node + node = new HashEntry<K,V>(hash, key, value, null); + retries = 0; + } + else if (key.equals(e.key)) + retries = 0; + else + e = e.next; + } + else if (++retries > MAX_SCAN_RETRIES) { + lock(); + break; + } + else if ((retries & 1) == 0 && + (f = entryForHash(this, hash)) != first) { + e = first = f; // re-traverse if entry changed + retries = -1; + } + } + return node; + } + + /** + * Scans for a node containing the given key while trying to + * acquire lock for a remove or replace operation. Upon + * return, guarantees that lock is held. Note that we must + * lock even if the key is not found, to ensure sequential + * consistency of updates. + */ + private void scanAndLock(Object key, int hash) { + // similar to but simpler than scanAndLockForPut + HashEntry<K,V> first = entryForHash(this, hash); + HashEntry<K,V> e = first; + int retries = -1; + while (!tryLock()) { + HashEntry<K,V> f; + if (retries < 0) { + if (e == null || key.equals(e.key)) + retries = 0; + else + e = e.next; + } + else if (++retries > MAX_SCAN_RETRIES) { + lock(); + break; + } + else if ((retries & 1) == 0 && + (f = entryForHash(this, hash)) != first) { + e = first = f; + retries = -1; + } + } + } + + /** * Remove; match on key only if value null, else match both. */ - V remove(Object key, int hash, Object value) { - lock(); + final V remove(Object key, int hash, Object value) { + if (!tryLock()) + scanAndLock(key, hash); + V oldValue = null; try { - int c = count - 1; HashEntry<K,V>[] tab = table; - int index = hash & (tab.length - 1); - HashEntry<K,V> first = tab[index]; - HashEntry<K,V> e = first; - while (e != null && (e.hash != hash || !key.equals(e.key))) - e = e.next; + int index = (tab.length - 1) & hash; + HashEntry<K,V> e = entryAt(tab, index); + HashEntry<K,V> pred = null; + while (e != null) { + K k; + HashEntry<K,V> next = e.next; + if ((k = e.key) == key || + (e.hash == hash && key.equals(k))) { + V v = e.value; + if (value == null || value == v || value.equals(v)) { + if (pred == null) + setEntryAt(tab, index, next); + else + pred.setNext(next); + ++modCount; + --count; + oldValue = v; + } + break; + } + pred = e; + e = next; + } + } finally { + unlock(); + } + return oldValue; + } - V oldValue = null; - if (e != null) { - V v = e.value; - if (value == null || value.equals(v)) { - oldValue = v; - // All entries following removed node can stay - // in list, but all preceding ones need to be - // cloned. + final boolean replace(K key, int hash, V oldValue, V newValue) { + if (!tryLock()) + scanAndLock(key, hash); + boolean replaced = false; + try { + HashEntry<K,V> e; + for (e = entryForHash(this, hash); e != null; e = e.next) { + K k; + if ((k = e.key) == key || + (e.hash == hash && key.equals(k))) { + if (oldValue.equals(e.value)) { + e.value = newValue; + ++modCount; + replaced = true; + } + break; + } + } + } finally { + unlock(); + } + return replaced; + } + + final V replace(K key, int hash, V value) { + if (!tryLock()) + scanAndLock(key, hash); + V oldValue = null; + try { + HashEntry<K,V> e; + for (e = entryForHash(this, hash); e != null; e = e.next) { + K k; + if ((k = e.key) == key || + (e.hash == hash && key.equals(k))) { + oldValue = e.value; + e.value = value; ++modCount; - HashEntry<K,V> newFirst = e.next; - for (HashEntry<K,V> p = first; p != e; p = p.next) - newFirst = new HashEntry<K,V>(p.key, p.hash, - newFirst, p.value); - tab[index] = newFirst; - count = c; // write-volatile + break; } } - return oldValue; + } finally { + unlock(); + } + return oldValue; + } + + final void clear() { + lock(); + try { + HashEntry<K,V>[] tab = table; + for (int i = 0; i < tab.length ; i++) + setEntryAt(tab, i, null); + ++modCount; + count = 0; } finally { unlock(); } } + } + + // Accessing segments + + /** + * Gets the jth element of given segment array (if nonnull) with + * volatile element access semantics via Unsafe. (The null check + * can trigger harmlessly only during deserialization.) Note: + * because each element of segments array is set only once (using + * fully ordered writes), some performance-sensitive methods rely + * on this method only as a recheck upon null reads. + */ + @SuppressWarnings("unchecked") + static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) { + long u = (j << SSHIFT) + SBASE; + return ss == null ? null : + (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u); + } - void clear() { - if (count != 0) { - lock(); - try { - HashEntry<K,V>[] tab = table; - for (int i = 0; i < tab.length ; i++) - tab[i] = null; - ++modCount; - count = 0; // write-volatile - } finally { - unlock(); + /** + * Returns the segment for the given index, creating it and + * recording in segment table (via CAS) if not already present. + * + * @param k the index + * @return the segment + */ + @SuppressWarnings("unchecked") + private Segment<K,V> ensureSegment(int k) { + final Segment<K,V>[] ss = this.segments; + long u = (k << SSHIFT) + SBASE; // raw offset + Segment<K,V> seg; + if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { + Segment<K,V> proto = ss[0]; // use segment 0 as prototype + int cap = proto.table.length; + float lf = proto.loadFactor; + int threshold = (int)(cap * lf); + HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry<?,?>[cap]; + if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) + == null) { // recheck + Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); + while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) + == null) { + if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) + break; } } } + return seg; } + // Hash-based segment and entry accesses + /** + * Gets the segment for the given hash code. + */ + @SuppressWarnings("unchecked") + private Segment<K,V> segmentForHash(int h) { + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u); + } + + /** + * Gets the table entry for the given segment and hash code. + */ + @SuppressWarnings("unchecked") + static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) { + HashEntry<K,V>[] tab; + return (seg == null || (tab = seg.table) == null) ? null : + (HashEntry<K,V>) UNSAFE.getObjectVolatile + (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + } /* ---------------- Public operations -------------- */ @@ -580,14 +699,13 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * negative or the load factor or concurrencyLevel are * nonpositive. */ + @SuppressWarnings("unchecked") public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); - if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; - // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; @@ -595,21 +713,23 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> ++sshift; ssize <<= 1; } - segmentShift = 32 - sshift; - segmentMask = ssize - 1; - this.segments = Segment.newArray(ssize); - + this.segmentShift = 32 - sshift; + this.segmentMask = ssize - 1; if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; - int cap = 1; + int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; - - for (int i = 0; i < this.segments.length; ++i) - this.segments[i] = new Segment<K,V>(cap, loadFactor); + // create segments and segments[0] + Segment<K,V> s0 = + new Segment<K,V>(loadFactor, (int)(cap * loadFactor), + (HashEntry<K,V>[])new HashEntry<?,?>[cap]); + Segment<K,V>[] ss = (Segment<K,V>[])new Segment<?,?>[ssize]; + UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] + this.segments = ss; } /** @@ -672,33 +792,36 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @return <tt>true</tt> if this map contains no key-value mappings */ public boolean isEmpty() { - final Segment<K,V>[] segments = this.segments; /* - * We keep track of per-segment modCounts to avoid ABA - * problems in which an element in one segment was added and - * in another removed during traversal, in which case the - * table was never actually empty at any point. Note the - * similar use of modCounts in the size() and containsValue() - * methods, which are the only other methods also susceptible - * to ABA problems. + * Sum per-segment modCounts to avoid mis-reporting when + * elements are concurrently added and removed in one segment + * while checking another, in which case the table was never + * actually empty at any point. (The sum ensures accuracy up + * through at least 1<<31 per-segment modifications before + * recheck.) Methods size() and containsValue() use similar + * constructions for stability checks. */ - int[] mc = new int[segments.length]; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0) - return false; - else - mcsum += mc[i] = segments[i].modCount; - } - // If mcsum happens to be zero, then we know we got a snapshot - // before any modifications at all were made. This is - // probably common enough to bother tracking. - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0 || - mc[i] != segments[i].modCount) + long sum = 0L; + final Segment<K,V>[] segments = this.segments; + for (int j = 0; j < segments.length; ++j) { + Segment<K,V> seg = segmentAt(segments, j); + if (seg != null) { + if (seg.count != 0) return false; + sum += seg.modCount; + } + } + if (sum != 0L) { // recheck unless no modifications + for (int j = 0; j < segments.length; ++j) { + Segment<K,V> seg = segmentAt(segments, j); + if (seg != null) { + if (seg.count != 0) + return false; + sum -= seg.modCount; + } } + if (sum != 0L) + return false; } return true; } @@ -711,45 +834,36 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @return the number of key-value mappings in this map */ public int size() { - final Segment<K,V>[] segments = this.segments; - long sum = 0; - long check = 0; - int[] mc = new int[segments.length]; // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. - for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { - check = 0; - sum = 0; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - sum += segments[i].count; - mcsum += mc[i] = segments[i].modCount; - } - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - check += segments[i].count; - if (mc[i] != segments[i].modCount) { - check = -1; // force retry - break; - } + final Segment<K,V>[] segments = this.segments; + final int segmentCount = segments.length; + + long previousSum = 0L; + for (int retries = -1; retries < RETRIES_BEFORE_LOCK; retries++) { + long sum = 0L; // sum of modCounts + long size = 0L; + for (int i = 0; i < segmentCount; i++) { + Segment<K,V> segment = segmentAt(segments, i); + if (segment != null) { + sum += segment.modCount; + size += segment.count; } } - if (check == sum) - break; + if (sum == previousSum) + return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE; + previousSum = sum; } - if (check != sum) { // Resort to locking all segments - sum = 0; - for (int i = 0; i < segments.length; ++i) - segments[i].lock(); - for (int i = 0; i < segments.length; ++i) - sum += segments[i].count; - for (int i = 0; i < segments.length; ++i) - segments[i].unlock(); + + long size = 0L; + for (int i = 0; i < segmentCount; i++) { + Segment<K,V> segment = ensureSegment(i); + segment.lock(); + size += segment.count; } - if (sum > Integer.MAX_VALUE) - return Integer.MAX_VALUE; - else - return (int)sum; + for (int i = 0; i < segmentCount; i++) + segments[i].unlock(); + return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE; } /** @@ -764,8 +878,21 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @throws NullPointerException if the specified key is null */ public V get(Object key) { - int hash = hash(key.hashCode()); - return segmentFor(hash).get(key, hash); + Segment<K,V> s; // manually integrate access methods to reduce overhead + HashEntry<K,V>[] tab; + int h = hash(key.hashCode()); + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && + (tab = s.table) != null) { + for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile + (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + e != null; e = e.next) { + K k; + if ((k = e.key) == key || (e.hash == h && key.equals(k))) + return e.value; + } + } + return null; } /** @@ -777,9 +904,23 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * <tt>equals</tt> method; <tt>false</tt> otherwise. * @throws NullPointerException if the specified key is null */ + @SuppressWarnings("unchecked") public boolean containsKey(Object key) { - int hash = hash(key.hashCode()); - return segmentFor(hash).containsKey(key, hash); + Segment<K,V> s; // same as get() except no need for volatile value read + HashEntry<K,V>[] tab; + int h = hash(key.hashCode()); + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && + (tab = s.table) != null) { + for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile + (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + e != null; e = e.next) { + K k; + if ((k = e.key) == key || (e.hash == h && key.equals(k))) + return true; + } + } + return false; } /** @@ -794,53 +935,47 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @throws NullPointerException if the specified value is null */ public boolean containsValue(Object value) { + // Same idea as size() if (value == null) throw new NullPointerException(); - - // See explanation of modCount use above - final Segment<K,V>[] segments = this.segments; - int[] mc = new int[segments.length]; - - // Try a few times without locking - for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { - int sum = 0; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - int c = segments[i].count; - mcsum += mc[i] = segments[i].modCount; - if (segments[i].containsValue(value)) - return true; - } - boolean cleanSweep = true; - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - int c = segments[i].count; - if (mc[i] != segments[i].modCount) { - cleanSweep = false; - break; - } - } - } - if (cleanSweep) - return false; - } - // Resort to locking all segments - for (int i = 0; i < segments.length; ++i) - segments[i].lock(); - boolean found = false; + long previousSum = 0L; + int lockCount = 0; try { - for (int i = 0; i < segments.length; ++i) { - if (segments[i].containsValue(value)) { - found = true; - break; + for (int retries = -1; ; retries++) { + long sum = 0L; // sum of modCounts + for (int j = 0; j < segments.length; j++) { + Segment<K,V> segment; + if (retries == RETRIES_BEFORE_LOCK) { + segment = ensureSegment(j); + segment.lock(); + lockCount++; + } else { + segment = segmentAt(segments, j); + if (segment == null) + continue; + } + HashEntry<K,V>[] tab = segment.table; + if (tab != null) { + for (int i = 0 ; i < tab.length; i++) { + HashEntry<K,V> e; + for (e = entryAt(tab, i); e != null; e = e.next) { + V v = e.value; + if (v != null && value.equals(v)) + return true; + } + } + sum += segment.modCount; + } } + if ((retries >= 0 && sum == previousSum) || lockCount > 0) + return false; + previousSum = sum; } } finally { - for (int i = 0; i < segments.length; ++i) - segments[i].unlock(); + for (int j = 0; j < lockCount; j++) + segments[j].unlock(); } - return found; } /** @@ -850,7 +985,7 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * full compatibility with class {@link java.util.Hashtable}, * which supported this method prior to introduction of the * Java Collections framework. - + * * @param value a value to search for * @return <tt>true</tt> if and only if some key maps to the * <tt>value</tt> argument in this table as @@ -875,11 +1010,17 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * <tt>null</tt> if there was no mapping for <tt>key</tt> * @throws NullPointerException if the specified key or value is null */ + @SuppressWarnings("unchecked") public V put(K key, V value) { + Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); - return segmentFor(hash).put(key, hash, value, false); + int j = (hash >>> segmentShift) & segmentMask; + if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck + (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment + s = ensureSegment(j); + return s.put(key, hash, value, false); } /** @@ -889,11 +1030,17 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * or <tt>null</tt> if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ + @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { + Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); - return segmentFor(hash).put(key, hash, value, true); + int j = (hash >>> segmentShift) & segmentMask; + if ((s = (Segment<K,V>)UNSAFE.getObject + (segments, (j << SSHIFT) + SBASE)) == null) + s = ensureSegment(j); + return s.put(key, hash, value, true); } /** @@ -919,7 +1066,8 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> */ public V remove(Object key) { int hash = hash(key.hashCode()); - return segmentFor(hash).remove(key, hash, null); + Segment<K,V> s = segmentForHash(hash); + return s == null ? null : s.remove(key, hash, null); } /** @@ -929,9 +1077,9 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> */ public boolean remove(Object key, Object value) { int hash = hash(key.hashCode()); - if (value == null) - return false; - return segmentFor(hash).remove(key, hash, value) != null; + Segment<K,V> s; + return value != null && (s = segmentForHash(hash)) != null && + s.remove(key, hash, value) != null; } /** @@ -940,10 +1088,11 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { + int hash = hash(key.hashCode()); if (oldValue == null || newValue == null) throw new NullPointerException(); - int hash = hash(key.hashCode()); - return segmentFor(hash).replace(key, hash, oldValue, newValue); + Segment<K,V> s = segmentForHash(hash); + return s != null && s.replace(key, hash, oldValue, newValue); } /** @@ -954,18 +1103,23 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { + int hash = hash(key.hashCode()); if (value == null) throw new NullPointerException(); - int hash = hash(key.hashCode()); - return segmentFor(hash).replace(key, hash, value); + Segment<K,V> s = segmentForHash(hash); + return s == null ? null : s.replace(key, hash, value); } /** * Removes all of the mappings from this map. */ public void clear() { - for (int i = 0; i < segments.length; ++i) - segments[i].clear(); + final Segment<K,V>[] segments = this.segments; + for (int j = 0; j < segments.length; ++j) { + Segment<K,V> s = segmentAt(segments, j); + if (s != null) + s.clear(); + } } /** @@ -1066,42 +1220,41 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> advance(); } - public boolean hasMoreElements() { return hasNext(); } - + /** + * Sets nextEntry to first node of next non-empty table + * (in backwards order, to simplify checks). + */ final void advance() { - if (nextEntry != null && (nextEntry = nextEntry.next) != null) - return; - - while (nextTableIndex >= 0) { - if ( (nextEntry = currentTable[nextTableIndex--]) != null) - return; - } - - while (nextSegmentIndex >= 0) { - Segment<K,V> seg = segments[nextSegmentIndex--]; - if (seg.count != 0) { - currentTable = seg.table; - for (int j = currentTable.length - 1; j >= 0; --j) { - if ( (nextEntry = currentTable[j]) != null) { - nextTableIndex = j - 1; - return; - } - } + for (;;) { + if (nextTableIndex >= 0) { + if ((nextEntry = entryAt(currentTable, + nextTableIndex--)) != null) + break; + } + else if (nextSegmentIndex >= 0) { + Segment<K,V> seg = segmentAt(segments, nextSegmentIndex--); + if (seg != null && (currentTable = seg.table) != null) + nextTableIndex = currentTable.length - 1; } + else + break; } } - public boolean hasNext() { return nextEntry != null; } - - HashEntry<K,V> nextEntry() { - if (nextEntry == null) + final HashEntry<K,V> nextEntry() { + HashEntry<K,V> e = nextEntry; + if (e == null) throw new NoSuchElementException(); - lastReturned = nextEntry; - advance(); - return lastReturned; + lastReturned = e; // cannot assign until after null check + if ((nextEntry = e.next) == null) + advance(); + return e; } - public void remove() { + public final boolean hasNext() { return nextEntry != null; } + public final boolean hasMoreElements() { return nextEntry != null; } + + public final void remove() { if (lastReturned == null) throw new IllegalStateException(); ConcurrentHashMap.this.remove(lastReturned.key); @@ -1113,16 +1266,16 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> extends HashIterator implements Iterator<K>, Enumeration<K> { - public K next() { return super.nextEntry().key; } - public K nextElement() { return super.nextEntry().key; } + public final K next() { return super.nextEntry().key; } + public final K nextElement() { return super.nextEntry().key; } } final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> { - public V next() { return super.nextEntry().value; } - public V nextElement() { return super.nextEntry().value; } + public final V next() { return super.nextEntry().value; } + public final V nextElement() { return super.nextEntry().value; } } /** @@ -1137,7 +1290,7 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> } /** - * Set our entry's value and write through to the map. The + * Sets our entry's value and writes through to the map. The * value to return is somewhat arbitrary here. Since a * WriteThroughEntry does not necessarily track asynchronous * changes, the most recent "previous" value could be @@ -1233,24 +1386,30 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> /* ---------------- Serialization Support -------------- */ /** - * Save the state of the <tt>ConcurrentHashMap</tt> instance to a - * stream (i.e., serialize it). + * Saves the state of the <tt>ConcurrentHashMap</tt> instance to a + * stream (i.e., serializes it). * @param s the stream * @serialData * the key (Object) and value (Object) * for each key-value mapping, followed by a null pair. * The key-value mappings are emitted in no particular order. */ - private void writeObject(java.io.ObjectOutputStream s) throws IOException { + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // force all segments for serialization compatibility + for (int k = 0; k < segments.length; ++k) + ensureSegment(k); s.defaultWriteObject(); + final Segment<K,V>[] segments = this.segments; for (int k = 0; k < segments.length; ++k) { - Segment<K,V> seg = segments[k]; + Segment<K,V> seg = segmentAt(segments, k); seg.lock(); try { HashEntry<K,V>[] tab = seg.table; for (int i = 0; i < tab.length; ++i) { - for (HashEntry<K,V> e = tab[i]; e != null; e = e.next) { + HashEntry<K,V> e; + for (e = entryAt(tab, i); e != null; e = e.next) { s.writeObject(e.key); s.writeObject(e.value); } @@ -1264,17 +1423,24 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> } /** - * Reconstitute the <tt>ConcurrentHashMap</tt> instance from a - * stream (i.e., deserialize it). + * Reconstitutes the <tt>ConcurrentHashMap</tt> instance from a + * stream (i.e., deserializes it). * @param s the stream */ + @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) - throws IOException, ClassNotFoundException { + throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); - // Initialize each segment to be minimally sized, and let grow. - for (int i = 0; i < segments.length; ++i) { - segments[i].setTable(new HashEntry[1]); + // Re-initialize segments to be minimally sized, and let grow. + int cap = MIN_SEGMENT_TABLE_CAPACITY; + final Segment<K,V>[] segments = this.segments; + for (int k = 0; k < segments.length; ++k) { + Segment<K,V> seg = segments[k]; + if (seg != null) { + seg.threshold = (int)(cap * seg.loadFactor); + seg.table = (HashEntry<K,V>[]) new HashEntry<?,?>[cap]; + } } // Read the keys and values, and put the mappings in the table @@ -1286,4 +1452,31 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> put(key, value); } } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long SBASE; + private static final int SSHIFT; + private static final long TBASE; + private static final int TSHIFT; + + static { + int ss, ts; + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + Class<?> tc = HashEntry[].class; + Class<?> sc = Segment[].class; + TBASE = UNSAFE.arrayBaseOffset(tc); + SBASE = UNSAFE.arrayBaseOffset(sc); + ts = UNSAFE.arrayIndexScale(tc); + ss = UNSAFE.arrayIndexScale(sc); + } catch (Exception e) { + throw new Error(e); + } + if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0) + throw new Error("data type scale not a power of two"); + SSHIFT = 31 - Integer.numberOfLeadingZeros(ss); + TSHIFT = 31 - Integer.numberOfLeadingZeros(ts); + } + } |