A version of Hashtable supporting concurrency for both retrievals and updates
/*
File: ConcurrentHashMap
Written by Doug Lea. Adapted and released, under explicit
permission, from JDK1.2 HashMap.java and Hashtable.java which
carries the following copyright:
* Copyright 1997 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
History:
Date Who What
26nov2000 dl Created, based on ConcurrentReaderHashMap
12jan2001 dl public release
17nov2001 dl Minor tunings
24oct2003 dl Segment implements Serializable
*/
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A version of Hashtable supporting concurrency for both retrievals and
* updates:
*
* <dl>
* <dt> Retrievals
*
* <dd> Retrievals may overlap updates. (This is the same policy as
* ConcurrentReaderHashMap.) Successful retrievals using get(key) and
* containsKey(key) usually run without locking. Unsuccessful retrievals (i.e.,
* when the key is not present) do involve brief synchronization (locking).
* Because retrieval operations can ordinarily overlap with update operations
* (i.e., put, remove, and their derivatives), retrievals can only be guaranteed
* to return the results of the most recently <em>completed</em> operations
* holding upon their onset. Retrieval operations may or may not return results
* reflecting in-progress writing operations. However, the retrieval operations
* do always return consistent results -- either those holding before any single
* modification or after it, but never a nonsense result. For aggregate
* operations such as putAll and clear, concurrent reads may reflect insertion
* or removal of only some entries.
* <p>
*
* Iterators and Enumerations (i.e., those returned by keySet().iterator(),
* entrySet().iterator(), values().iterator(), keys(), and elements()) return
* elements reflecting the state of the hash table at some point at or since the
* creation of the iterator/enumeration. They will return at most one instance
* of each element (via next()/nextElement()), but might or might not reflect
* puts and removes that have been processed since they were created. They do
* <em>not</em> throw ConcurrentModificationException. However, these
* iterators are designed to be used by only one thread at a time. Passing an
* iterator across multiple threads may lead to unpredictable results if the
* table is being concurrently modified.
* <p>
*
*
* <dt> Updates
*
* <dd> This class supports a hard-wired preset <em>concurrency
* level</em> of
* 32. This allows a maximum of 32 put and/or remove operations to proceed
* concurrently. This level is an upper bound on concurrency, not a guarantee,
* since it interacts with how well-strewn elements are across bins of the
* table. (The preset value in part reflects the fact that even on large
* multiprocessors, factors other than synchronization tend to be bottlenecks
* when more than 32 threads concurrently attempt updates.) Additionally,
* operations triggering internal resizing and clearing do not execute
* concurrently with any operation.
* <p>
*
* There is <em>NOT</em> any support for locking the entire table to prevent
* updates. This makes it imposssible, for example, to add an element only if it
* is not already present, since another thread may be in the process of doing
* the same thing. If you need such capabilities, consider instead using the
* ConcurrentReaderHashMap class.
*
* </dl>
*
* Because of how concurrency control is split up, the size() and isEmpty()
* methods require accumulations across 32 control segments, and so might be
* slightly slower than you expect.
* <p>
*
* This class may be used as a direct replacement for java.util.Hashtable in any
* application that does not rely on the ability to lock the entire table to
* prevent updates. As of this writing, it performs much faster than Hashtable
* in typical multi-threaded applications with multiple readers and writers.
* Like Hashtable but unlike java.util.HashMap, this class does NOT allow
* <tt>null</tt> to be used as a key or value.
* <p>
*
* Implementation note: A slightly faster implementation of this class will be
* possible once planned Java Memory Model revisions are in place.
*
* <p>[<a
* href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html">
* Introduction to this package. </a>]
*
*/
public class ConcurrentHashMap extends AbstractMap implements Map, Cloneable, Serializable
{
private static final long serialVersionUID = 1L;
/*
* The basic strategy is an optimistic-style scheme based on the guarantee
* that the hash table and its lists are always kept in a consistent enough
* state to be read without locking:
*
* Read operations first proceed without locking, by traversing the
* apparently correct list of the apparently correct bin. If an entry is
* found, but not invalidated (value field null), it is returned. If not
* found, operations must recheck (after a memory barrier) to make sure they
* are using both the right list and the right table (which can change under
* resizes). If invalidated, reads must acquire main update lock to wait out
* the update, and then re-traverse.
*
* All list additions are at the front of each bin, making it easy to check
* changes, and also fast to traverse. Entry next pointers are never
* assigned. Remove() builds new nodes when necessary to preserve this.
*
* Remove() (also clear()) invalidates removed nodes to alert read
* operations that they must wait out the full modifications.
*
* Locking for puts, removes (and, when necessary gets, etc) is controlled
* by Segments, each covering a portion of the table. During operations
* requiring global exclusivity (mainly resize and clear), ALL of these
* locks are acquired at once. Note that these segments are NOT contiguous --
* they are based on the least 5 bits of hashcodes. This ensures that the
* same segment controls the same slots before and after resizing, which is
* necessary for supporting concurrent retrievals. This comes at the price
* of a mismatch of logical vs physical locality, but this seems not to be a
* performance problem in practice.
*
*/
/**
* The hash table data.
*/
protected transient Entry[] table;
/**
* The number of concurrency control segments. The value can be at most 32
* since ints are used as bitsets over segments. Emprically, it doesn't seem
* to pay to decrease it either, so the value should be at least 32. In
* other words, do not redefine this :-)
*/
protected static final int CONCURRENCY_LEVEL = 32;
/**
* Mask value for indexing into segments
*/
protected static final int SEGMENT_MASK = CONCURRENCY_LEVEL - 1;
/**
* Bookkeeping for each concurrency control segment. Each segment contains a
* local count of the number of elements in its region. However, the main
* use of a Segment is for its lock.
*/
protected final static class Segment implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* The number of elements in this segment's region. It is always updated
* within synchronized blocks.
*/
protected int count;
/**
* Get the count under synch.
* @return count under sync
*/
protected synchronized int getCount()
{
return count;
}
/**
* Force a synchronization
*/
protected synchronized void synch()
{
}
}
/**
* The array of concurrency control segments.
*/
protected final Segment[] segments = new Segment[CONCURRENCY_LEVEL];
/**
* The default initial number of table slots for this table (32). Used when
* not otherwise specified in constructor.
*/
public static final int DEFAULT_INITIAL_CAPACITY = 32;
/**
* The minimum capacity, used if a lower value is implicitly specified by
* either of the constructors with arguments. MUST be a power of two.
*/
private static final int MINIMUM_CAPACITY = 32;
/**
* The maximum capacity, used if a higher value is implicitly specified by
* either of the constructors with arguments. MUST be a power of two <= 1<<30.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The default load factor for this table (0.75) Used when not otherwise
* specified in constructor.
*/
public static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The load factor for the hash table.
*
* @serial
*/
protected final float loadFactor;
/**
* Per-segment resize threshold.
*
* @serial
*/
protected int threshold;
/**
* Number of segments voting for resize. The table is doubled when 1/4 of
* the segments reach threshold. Volatile but updated without synch since
* this is just a heuristic.
*/
protected transient volatile int votesForResize;
/**
* Return the number of set bits in w. For a derivation of this algorithm,
* see "Algorithms and data structures with applications to graphics and
* geometry", by Jurg Nievergelt and Klaus Hinrichs, Prentice Hall, 1993.
* See also notes by Torsten Sillke at
* http://www.mathematik.uni-bielefeld.de/~sillke/PROBLEMS/bitcount
* @param w arg
* @return number of set bits
*/
protected static int bitcount(int w)
{
w -= (0xaaaaaaaa & w) >>> 1;
w = (w & 0x33333333) + ((w >>> 2) & 0x33333333);
w = (w + (w >>> 4)) & 0x0f0f0f0f;
w += w >>> 8;
w += w >>> 16;
return w & 0xff;
}
/**
* Returns the appropriate capacity (power of two) for the specified initial
* capacity argument.
* @param initialCapacity the initial capacity
* @return appropriate capacity
*/
private int p2capacity(int initialCapacity)
{
int cap = initialCapacity;
// Compute the appropriate capacity
int result;
if (cap > MAXIMUM_CAPACITY || cap < 0)
{
result = MAXIMUM_CAPACITY;
}
else
{
result = MINIMUM_CAPACITY;
while (result < cap)
{
result <<= 1;
}
}
return result;
}
/**
* Return hash code for Object x. Since we are using power-of-two tables, it
* is worth the effort to improve hashcode via the same multiplicative
* scheme as used in IdentityHashMap.
* @param x
* @return hash code
*/
protected static int hash(Object x)
{
int h = x.hashCode();
// Multiply by 127 (quickly, via shifts), and mix in some high
// bits to help guard against bunching of codes that are
// consecutive or equally spaced.
return ((h << 7) - h + (h >>> 9) + (h >>> 17));
}
/**
* Check for equality of non-null references x and y.
* @param x ref
* @param y ref
* @return is equal
*/
protected boolean eq(Object x, Object y)
{
return x == y || x.equals(y);
}
/**
* Create table array and set the per-segment threshold *
* @param capacity
* @return table array
*/
protected Entry[] newTable(int capacity)
{
threshold = (int)(capacity * loadFactor / CONCURRENCY_LEVEL) + 1;
return new Entry[capacity];
}
/**
* Constructs a new, empty map with the specified initial capacity and the
* specified load factor.
*
* @param initialCapacity
* the initial capacity. The actual initial capacity is rounded
* to the nearest power of two.
* @param loadFactor
* the load factor threshold, used to control resizing. This
* value is used in an approximate way: When at least a quarter
* of the segments of the table reach per-segment threshold, or
* one of the segments itself exceeds overall threshold, the
* table is doubled. This will on average cause resizing when the
* table-wide load factor is slightly less than the threshold. If
* you'd like to avoid resizing, you can set this to a
* ridiculously large value.
* @throws IllegalArgumentException
* if the load factor is nonpositive.
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor)
{
if (!(loadFactor > 0))
{
throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
}
this.loadFactor = loadFactor;
for (int i = 0; i < segments.length; ++i)
{
segments[i] = new Segment();
}
int cap = p2capacity(initialCapacity);
table = newTable(cap);
}
/**
* Constructs a new, empty map with the specified initial capacity and
* default load factor.
*
* @param initialCapacity
* the initial capacity of the ConcurrentHashMap.
* @throws IllegalArgumentException
* if the initial maximum number of elements is less than zero.
*/
public ConcurrentHashMap(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty map with a default initial capacity and default
* load factor.
*/
public ConcurrentHashMap()
{
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new map with the same mappings as the given map. The map is
* created with a capacity of twice the number of mappings in the given map
* or 32 (whichever is greater), and a default load factor.
* @param t map to copy
*/
public ConcurrentHashMap(Map t)
{
this(Math.max((int)(t.size() / DEFAULT_LOAD_FACTOR) + 1, MINIMUM_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(t);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
public int size()
{
int c = 0;
for (int i = 0; i < segments.length; ++i)
{
c += segments[i].getCount();
}
return c;
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings.
*/
public boolean isEmpty()
{
for (int i = 0; i < segments.length; ++i)
{
if (segments[i].getCount() != 0)
{
return false;
}
}
return true;
}
/**
* Returns the value to which the specified key is mapped in this table.
*
* @param key
* a key in the table.
* @return the value to which the key is mapped in this table;
* <code>null</code> if the key is not mapped to any value in this
* table.
* @exception NullPointerException
* if the key is <code>null</code>.
* @see #put(Object, Object)
*/
public Object get(Object key)
{
int hash = hash(key); // throws null pointer exception if key null
// Try first without locking...
Entry[] tab = table;
int index = hash & (tab.length - 1);
Entry first = tab[index];
Entry e;
for (e = first; e != null; e = e.next)
{
if (e.hash == hash && eq(key, e.key))
{
Object value = e.value;
if (value != null)
{
return value;
}
else
{
break;
}
}
}
// Recheck under synch if key apparently not there or interference
Segment seg = segments[hash & SEGMENT_MASK];
synchronized (seg)
{
tab = table;
index = hash & (tab.length - 1);
Entry newFirst = tab[index];
if (e != null || first != newFirst)
{
for (e = newFirst; e != null; e = e.next)
{
if (e.hash == hash && eq(key, e.key))
{
return e.value;
}
}
}
return null;
}
}
/**
* Tests if the specified object is a key in this table.
*
* @param key
* possible key.
* @return <code>true</code> if and only if the specified object is a key
* in this table, as determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @exception NullPointerException
* if the key is <code>null</code>.
* @see #contains(Object)
*/
public boolean containsKey(Object key)
{
return get(key) != null;
}
/**
* Maps the specified <code>key</code> to the specified <code>value</code>
* in this table. Neither the key nor the value can be <code>null</code>.
* (Note that this policy is the same as for java.util.Hashtable, but unlike
* java.util.HashMap, which does accept nulls as valid keys and values.)
* <p>
*
* The value can be retrieved by calling the <code>get</code> method with
* a key that is equal to the original key.
*
* @param key
* the table key.
* @param value
* the value.
* @return the previous value of the specified key in this table, or
* <code>null</code> if it did not have one.
* @exception NullPointerException
* if the key or value is <code>null</code>.
* @see Object#equals(Object)
* @see #get(Object)
*/
public Object put(Object key, Object value)
{
if (value == null)
{
throw new IllegalArgumentException("Value must not be null");
}
int hash = hash(key);
Segment seg = segments[hash & SEGMENT_MASK];
int segcount;
Entry[] tab;
int votes;
synchronized (seg)
{
tab = table;
int index = hash & (tab.length - 1);
Entry first = tab[index];
for (Entry e = first; e != null; e = e.next)
{
if (e.hash == hash && eq(key, e.key))
{
Object oldValue = e.value;
e.value = value;
return oldValue;
}
}
// Add to front of list
Entry newEntry = new Entry(hash, key, value, first);
tab[index] = newEntry;
if ((segcount = ++seg.count) < threshold)
{
return null;
}
int bit = (1 << (hash & SEGMENT_MASK));
votes = votesForResize;
if ((votes & bit) == 0)
{
votes = votesForResize |= bit;
}
}
// Attempt resize if 1/4 segs vote,
// or if this seg itself reaches the overall threshold.
// (The latter check is just a safeguard to avoid pathological cases.)
if (bitcount(votes) >= CONCURRENCY_LEVEL / 4 || segcount > (threshold * CONCURRENCY_LEVEL))
{
resize(0, tab);
}
return null;
}
/**
* Gather all locks in order to call rehash, by recursing within synch
* blocks for each segment index.
*
* @param index
* the current segment. initially call value must be 0
* @param assumedTab
* the state of table on first call to resize. If this changes on
* any call, the attempt is aborted because the table has already
* been resized by another thread.
*/
protected void resize(int index, Entry[] assumedTab)
{
Segment seg = segments[index];
synchronized (seg)
{
if (assumedTab == table)
{
int next = index + 1;
if (next < segments.length)
{
resize(next, assumedTab);
}
else
{
rehash();
}
}
}
}
/**
* Rehashes the contents of this map into a new table with a larger
* capacity.
*/
protected void rehash()
{
votesForResize = 0; // reset
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY)
{
threshold = Integer.MAX_VALUE; // avoid retriggering
return;
}
int newCapacity = oldCapacity << 1;
Entry[] newTable = newTable(newCapacity);
int mask = newCapacity - 1;
/*
* 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 to oldCapacity+index. We also eliminate
* unnecessary node creation by catching cases where old nodes can be
* reused because their next fields won't change. Statistically, at the
* default threshhold, only about one-sixth of them need cloning. (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.)
*/
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.
Entry e = oldTable[i];
if (e != null)
{
int idx = e.hash & mask;
Entry next = e.next;
// Single node on list
if (next == null)
{
newTable[idx] = e;
}
else
{
// Reuse trailing consecutive sequence of all same bit
Entry lastRun = e;
int lastIdx = idx;
for (Entry last = next; last != null; last = last.next)
{
int k = last.hash & mask;
if (k != lastIdx)
{
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone all remaining nodes
for (Entry p = e; p != lastRun; p = p.next)
{
int k = p.hash & mask;
newTable[k] = new Entry(p.hash, p.key, p.value, newTable[k]);
}
}
}
}
table = newTable;
}
/**
* Removes the key (and its corresponding value) from this table. This
* method does nothing if the key is not in the table.
*
* @param key
* the key that needs to be removed.
* @return the value to which the key had been mapped in this table, or
* <code>null</code> if the key did not have a mapping.
* @exception NullPointerException
* if the key is <code>null</code>.
*/
public Object remove(Object key)
{
return remove(key, null);
}
/**
* Removes the (key, value) pair from this table. This method does nothing
* if the key is not in the table, or if the key is associated with a
* different value. This method is needed by EntrySet.
*
* @param key
* the key that needs to be removed.
* @param value
* the associated value. If the value is null, it means "any
* value".
* @return the value to which the key had been mapped in this table, or
* <code>null</code> if the key did not have a mapping.
* @exception NullPointerException
* if the key is <code>null</code>.
*/
protected Object remove(Object key, Object value)
{
/*
* Find the entry, then 1. Set value field to null, to force get() to
* retry 2. Rebuild the list without this entry. All entries following
* removed node can stay in list, but all preceeding ones need to be
* cloned. Traversals rely on this strategy to ensure that elements will
* not be repeated during iteration.
*/
int hash = hash(key);
Segment seg = segments[hash & SEGMENT_MASK];
synchronized (seg)
{
Entry[] tab = table;
int index = hash & (tab.length - 1);
Entry first = tab[index];
Entry e = first;
for (;;)
{
if (e == null)
{
return null;
}
if (e.hash == hash && eq(key, e.key))
{
break;
}
e = e.next;
}
Object oldValue = e.value;
if (value != null && !value.equals(oldValue))
{
return null;
}
e.value = null;
Entry head = e.next;
for (Entry p = first; p != e; p = p.next)
{
head = new Entry(p.hash, p.key, p.value, head);
}
tab[index] = head;
seg.count--;
return oldValue;
}
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value. Note: This method requires a full internal traversal of
* the hash table, and so is much slower than method <tt>containsKey</tt>.
*
* @param value
* value whose presence in this map is to be tested.
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value.
* @exception NullPointerException
* if the value is <code>null</code>.
*/
public boolean containsValue(Object value)
{
if (value == null)
{
throw new IllegalArgumentException("Value must not be null");
}
for (int s = 0; s < segments.length; ++s)
{
Segment seg = segments[s];
Entry[] tab;
synchronized (seg)
{
tab = table;
}
for (int i = s; i < tab.length; i += segments.length)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
if (value.equals(e.value))
{
return true;
}
}
}
}
return false;
}
/**
* Tests if some key maps into the specified value in this table. This
* operation is more expensive than the <code>containsKey</code> method.
* <p>
*
* Note that this method is identical in functionality to containsValue,
* (which is part of the Map interface in the collections framework).
*
* @param value
* a value to search for.
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this table as determined by the
* <tt>equals</tt> method; <code>false</code> otherwise.
* @exception NullPointerException
* if the value is <code>null</code>.
* @see #containsKey(Object)
* @see #containsValue(Object)
* @see Map
*/
public boolean contains(Object value)
{
return containsValue(value);
}
/**
* Copies all of the mappings from the specified map to this one.
*
* These mappings replace any mappings that this map had for any of the keys
* currently in the specified Map.
*
* @param t
* Mappings to be stored in this map.
*/
public void putAll(Map t)
{
int n = t.size();
if (n == 0)
{
return;
}
// Expand enough to hold at least n elements without resizing.
// We can only resize table by factor of two at a time.
// It is faster to rehash with fewer elements, so do it now.
for (;;)
{
Entry[] tab;
int max;
synchronized (segments[0])
{ // must synch on some segment. pick 0.
tab = table;
max = threshold * CONCURRENCY_LEVEL;
}
if (n < max)
{
break;
}
resize(0, tab);
}
for (Iterator it = t.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry)it.next();
put(entry.getKey(), entry.getValue());
}
}
/**
* Removes all mappings from this map.
*/
public void clear()
{
// We don't need all locks at once so long as locks
// are obtained in low to high order
for (int s = 0; s < segments.length; ++s)
{
Segment seg = segments[s];
synchronized (seg)
{
Entry[] tab = table;
for (int i = s; i < tab.length; i += segments.length)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
e.value = null;
}
tab[i] = null;
seg.count = 0;
}
}
}
}
/**
* Returns a shallow copy of this <tt>ConcurrentHashMap</tt> instance: the
* keys and values themselves are not cloned.
*
* @return a shallow copy of this map.
*/
public Object clone()
{
// We cannot call super.clone, since it would share final segments
// array,
// and there's no way to reassign finals.
return new ConcurrentHashMap(this);
}
// Views
protected transient Set keySet = null;
protected transient Set entrySet = null;
protected transient Collection values = null;
/**
* Returns a set view of the keys contained in this map. The set is backed
* by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* @return a set view of the keys contained in this map.
*/
public Set keySet()
{
Set ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
private class KeySet extends AbstractSet
{
/**
* @see java.util.Set#iterator()
*/
public Iterator iterator()
{
return new KeyIterator();
}
/**
* @see java.util.Set#size()
*/
public int size()
{
return ConcurrentHashMap.this.size();
}
/**
* @see java.util.Set#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
return ConcurrentHashMap.this.containsKey(o);
}
/**
* @see java.util.Set#remove(java.lang.Object)
*/
public boolean remove(Object o)
{
return ConcurrentHashMap.this.remove(o) != null;
}
/**
* @see java.util.Set#clear()
*/
public void clear()
{
ConcurrentHashMap.this.clear();
}
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a collection view of the values contained in this map.
*/
public Collection values()
{
Collection vs = values;
return (vs != null) ? vs : (values = new Values());
}
private class Values extends AbstractCollection
{
/**
* @see java.util.AbstractCollection#iterator()
*/
public Iterator iterator()
{
return new ValueIterator();
}
/**
* @see java.util.AbstractCollection#size()
*/
public int size()
{
return ConcurrentHashMap.this.size();
}
/**
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
return ConcurrentHashMap.this.containsValue(o);
}
/**
* @see java.util.AbstractCollection#clear()
*/
public void clear()
{
ConcurrentHashMap.this.clear();
}
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a collection view of the mappings contained in this map.
*/
public Set entrySet()
{
Set es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
private class EntrySet extends AbstractSet
{
/**
* @see java.util.Set#iterator()
*/
public Iterator iterator()
{
return new HashIterator();
}
/**
* @see java.util.Set#contains(java.lang.Object)
*/
public boolean contains(Object o)
{
if (!(o instanceof Map.Entry))
{
return false;
}
Map.Entry entry = (Map.Entry)o;
Object v = ConcurrentHashMap.this.get(entry.getKey());
return v != null && v.equals(entry.getValue());
}
/**
* @see java.util.Set#remove(java.lang.Object)
*/
public boolean remove(Object o)
{
if (!(o instanceof Map.Entry))
{
return false;
}
Map.Entry e = (Map.Entry)o;
return ConcurrentHashMap.this.remove(e.getKey(), e.getValue()) != null;
}
/**
* @see java.util.Set#size()
*/
public int size()
{
return ConcurrentHashMap.this.size();
}
/**
* @see java.util.Set#clear()
*/
public void clear()
{
ConcurrentHashMap.this.clear();
}
}
/**
* Returns an enumeration of the keys in this table.
*
* @return an enumeration of the keys in this table.
* @see Enumeration
* @see #elements()
* @see #keySet()
* @see Map
*/
public Enumeration keys()
{
return new KeyIterator();
}
/**
* Returns an enumeration of the values in this table. Use the Enumeration
* methods on the returned object to fetch the elements sequentially.
*
* @return an enumeration of the values in this table.
* @see java.util.Enumeration
* @see #keys()
* @see #values()
* @see Map
*/
public Enumeration elements()
{
return new ValueIterator();
}
/**
* ConcurrentHashMap collision list entry.
*/
protected static class Entry implements Map.Entry
{
/*
* The use of volatile for value field ensures that we can detect status
* changes without synchronization. The other fields are never changed,
* and are marked as final.
*/
protected final Object key;
protected volatile Object value;
protected final int hash;
protected final Entry next;
Entry(int hash, Object key, Object value, Entry next)
{
this.value = value;
this.hash = hash;
this.key = key;
this.next = next;
}
// Map.Entry Ops
/**
* @see java.util.Map.Entry#getKey()
*/
public Object getKey()
{
return key;
}
/**
* Get the value. Note: In an entrySet or entrySet.iterator, unless you
* can guarantee lack of concurrent modification,
* <tt>getValue</tt> <em>might</em> return null, reflecting the fact
* that the entry has been concurrently removed. However, there are no
* assurances that concurrent removals will be reflected using this
* method.
*
* @return the current value, or null if the entry has been detectably
* removed.
*/
public Object getValue()
{
return value;
}
/**
* Set the value of this entry. Note: In an entrySet or
* entrySet.iterator), unless you can guarantee lack of concurrent
* modification, <tt>setValue</tt> is not strictly guaranteed to
* actually replace the value field obtained via the <tt>get</tt>
* operation of the underlying hash table in multithreaded applications.
* If iterator-wide synchronization is not used, and any other
* concurrent <tt>put</tt> or <tt>remove</tt> operations occur,
* sometimes even to <em>other</em> entries, then this change is not
* guaranteed to be reflected in the hash table. (It might, or it might
* not. There are no assurances either way.)
*
* @param value
* the new value.
* @return the previous value, or null if entry has been detectably
* removed.
* @exception NullPointerException
* if the value is <code>null</code>.
*
*/
public Object setValue(Object value)
{
if (value == null)
{
throw new IllegalArgumentException("Value must not be null");
}
Object oldValue = this.value;
this.value = value;
return oldValue;
}
/**
* @see java.util.Map.Entry#equals(java.lang.Object)
*/
public boolean equals(Object o)
{
if (!(o instanceof Map.Entry))
{
return false;
}
Map.Entry e = (Map.Entry)o;
return (key.equals(e.getKey()) && value.equals(e.getValue()));
}
/**
* @see java.util.Map.Entry#hashCode()
*/
public int hashCode()
{
return key.hashCode() ^ value.hashCode();
}
/**
* @see java.lang.Object#toString()
*/
public String toString()
{
return key + "=" + value;
}
}
protected class HashIterator implements Iterator, Enumeration
{
protected final Entry[] tab; // snapshot of table
protected int index; // current slot
protected Entry entry = null; // current node of slot
protected Object currentKey; // key for current node
protected Object currentValue; // value for current node
protected Entry lastReturned = null; // last node returned by next
protected HashIterator()
{
// force all segments to synch
synchronized (segments[0])
{
tab = table;
}
for (int i = 1; i < segments.length; ++i)
{
segments[i].synch();
}
index = tab.length - 1;
}
/**
* @see java.util.Enumeration#hasMoreElements()
*/
public boolean hasMoreElements()
{
return hasNext();
}
/**
* @see java.util.Enumeration#nextElement()
*/
public Object nextElement()
{
return next();
}
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext()
{
/*
* currentkey and currentValue are set here to ensure that next()
* returns normally if hasNext() returns true. This avoids surprises
* especially when final element is removed during traversal --
* instead, we just ignore the removal during current traversal.
*/
for (;;)
{
if (entry != null)
{
Object v = entry.value;
if (v != null)
{
currentKey = entry.key;
currentValue = v;
return true;
}
else
{
entry = entry.next;
}
}
while (entry == null && index >= 0)
{
entry = tab[index--];
}
if (entry == null)
{
currentKey = currentValue = null;
return false;
}
}
}
protected Object returnValueOfNext()
{
return entry;
}
/**
* @see java.util.Iterator#next()
*/
public Object next()
{
if (currentKey == null && !hasNext())
{
throw new NoSuchElementException();
}
Object result = returnValueOfNext();
lastReturned = entry;
currentKey = currentValue = null;
entry = entry.next;
return result;
}
/**
* @see java.util.Iterator#remove()
*/
public void remove()
{
if (lastReturned == null)
{
throw new IllegalStateException();
}
ConcurrentHashMap.this.remove(lastReturned.key);
lastReturned = null;
}
}
protected class KeyIterator extends HashIterator
{
protected Object returnValueOfNext()
{
return currentKey;
}
}
protected class ValueIterator extends HashIterator
{
protected Object returnValueOfNext()
{
return currentValue;
}
}
/**
* Save the state of the <tt>ConcurrentHashMap</tt> instance to a stream
* (i.e., serialize it).
* @param s
* @throws IOException
*
* @serialData An estimate of the table size, followed by 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
{
// Write out the loadfactor, and any hidden stuff
s.defaultWriteObject();
// Write out capacity estimate. It is OK if this
// changes during the write, since it is only used by
// readObject to set initial capacity, to avoid needless resizings.
int cap;
synchronized (segments[0])
{
cap = table.length;
}
s.writeInt(cap);
// Write out keys and values (alternating)
for (int k = 0; k < segments.length; ++k)
{
Segment seg = segments[k];
Entry[] tab;
synchronized (seg)
{
tab = table;
}
for (int i = k; i < tab.length; i += segments.length)
{
for (Entry e = tab[i]; e != null; e = e.next)
{
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}
s.writeObject(null);
s.writeObject(null);
}
/**
* Reconstitute the <tt>ConcurrentHashMap</tt> instance from a stream
* (i.e., deserialize it).
* @param s
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
{
// Read in the threshold, loadfactor, and any hidden stuff
s.defaultReadObject();
int cap = s.readInt();
table = newTable(cap);
for (int i = 0; i < segments.length; ++i)
{
segments[i] = new Segment();
}
// Read the keys and values, and put the mappings in the table
for (;;)
{
Object key = s.readObject();
Object value = s.readObject();
if (key == null)
{
break;
}
put(key, value);
}
}
}
Related examples in the same category