/*
* Copyright (C) 2009 Google Inc. Licensed under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.github.droidfu.com.google.common.collect;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.ReentrantLock;
import com.github.droidfu.com.google.common.base.Function;
import com.github.droidfu.com.google.common.collect.AbstractMapEntry;
import com.github.droidfu.com.google.common.collect.ComputationException;
/**
* A framework for concurrent hash map implementations. The
* CustomConcurrentHashMap class itself is not extensible and does not contain
* any methods. Use {@link Builder} to create a custom concurrent hash map
* instance. Client libraries implement {@link Strategy}, and this class
* provides the surrounding concurrent data structure which implements
* {@link ConcurrentMap}. Additionally supports implementing maps where
* {@link Map#get} atomically computes values on demand (see
* {@link Builder#buildComputingMap(CustomConcurrentHashMap.ComputingStrategy, Function)}
* ).
* <p>
* The resulting hash table supports full concurrency of retrievals and
* adjustable expected concurrency for updates. Even though all operations are
* thread-safe, retrieval operations do <i>not</i> entail locking, and there is
* <i>not</i> any support for locking the entire table in a way that prevents
* all access.
* <p>
* Retrieval operations (including {@link Map#get}) generally do not block, so
* may overlap with update operations (including {@link Map#put} and
* {@link Map#remove}). Retrievals reflect the results of the most recently
* <i>completed</i> update operations holding upon their onset. For aggregate
* operations such as {@link Map#putAll} and {@link Map#clear}, concurrent
* retrievals may reflect insertion or removal of only some entries. Similarly,
* iterators return elements reflecting the state of the hash table at some
* point at or since the creation of the iterator. They do <i>not</i> throw
* {@link java.util.ConcurrentModificationException}. However, iterators can
* only be used by one thread at a time.
* <p>
* The resulting {@link ConcurrentMap} and its views and iterators implement all
* of the <i>optional</i> methods of the {@link java.util.Map} and
* {@link java.util.Iterator} interfaces. Partially reclaimed entries are never
* exposed through the views or iterators.
* <p>
* For example, the following strategy emulates the behavior of
* {@link java.util.concurrent.ConcurrentHashMap}:
*
* <pre>
* @code
* class ConcurrentHashMapStrategy<K, V>
* implements CustomConcurrentHashMap.Strategy<K, V,
* InternalEntry<K, V>>, Serializable {
* public InternalEntry<K, V> newEntry(K key, int hash,
* InternalEntry<K, V> next) {
* return new InternalEntry<K, V>(key, hash, null, next);
* }
* public InternalEntry<K, V> copyEntry(K key,
* InternalEntry<K, V> original, InternalEntry<K, V> next) {
* return new InternalEntry<K, V>(key, original.hash, original.value, next);
* }
* public void setValue(InternalEntry<K, V> entry, V value) {
* entry.value = value;
* }
* public V getValue(InternalEntry<K, V> entry) { return entry.value; }
* public boolean equalKeys(K a, Object b) { return a.equals(b); }
* public boolean equalValues(V a, Object b) { return a.equals(b); }
* public int hashKey(Object key) { return key.hashCode(); }
* public K getKey(InternalEntry<K, V> entry) { return entry.key; }
* public InternalEntry<K, V> getNext(InternalEntry<K, V> entry) {
* return entry.next;
* }
* public int getHash(InternalEntry<K, V> entry) { return entry.hash; }
* public void setInternals(CustomConcurrentHashMap.Internals<K, V,
* InternalEntry<K, V>> internals) {} // ignored
* }
* class InternalEntry<K, V> {
* final K key;
* final int hash;
* volatile V value;
* final InternalEntry<K, V> next;
* InternalEntry(K key, int hash, V value, InternalEntry<K, V> next) {
* this.key = key;
* this.hash = hash;
* this.value = value;
* this.next = next;
* }
* }
* }
* </pre>
*
* To create a {@link java.util.concurrent.ConcurrentMap} using the strategy
* above:
*
* <pre>
* @code
* ConcurrentMap<K, V> map = new CustomConcurrentHashMap.Builder()
* .build(new ConcurrentHashMapStrategy<K, V>());
* }
* </pre>
*
* @author Bob Lee
* @author Doug Lea
*/
final class CustomConcurrentHashMap {
/** Prevents instantiation. */
private CustomConcurrentHashMap() {
}
/**
* Builds a custom concurrent hash map.
*/
static final class Builder {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
private static final int UNSET_INITIAL_CAPACITY = -1;
private static final int UNSET_CONCURRENCY_LEVEL = -1;
int initialCapacity = UNSET_INITIAL_CAPACITY;
int concurrencyLevel = UNSET_CONCURRENCY_LEVEL;
/**
* Sets a custom initial capacity (defaults to 16). Resizing this or any
* other kind of hash table is a relatively slow operation, so, when
* possible, it is a good idea to provide estimates of expected table
* sizes.
*
* @throws IllegalArgumentException
* if initialCapacity < 0
*/
public Builder initialCapacity(int initialCapacity) {
if (this.initialCapacity != UNSET_INITIAL_CAPACITY) {
throw new IllegalStateException("initial capacity was already set to "
+ this.initialCapacity);
}
if (initialCapacity < 0) {
throw new IllegalArgumentException();
}
this.initialCapacity = initialCapacity;
return this;
}
/**
* Guides the allowed concurrency among update operations. Used as a
* hint for internal sizing. The table is internally partitioned to try
* to permit the indicated number of concurrent updates without
* contention. Because placement in hash tables is essentially random,
* the actual concurrency will vary. Ideally, you should choose a value
* to accommodate as many threads as will ever concurrently modify the
* table. Using a significantly higher value than you need can waste
* space and time, and a significantly lower value can lead to thread
* contention. But overestimates and underestimates within an order of
* magnitude do not usually have much noticeable impact. A value of one
* is appropriate when it is known that only one thread will modify and
* all others will only read. Defaults to {@literal 16}.
*
* @throws IllegalArgumentException
* if concurrencyLevel < 0
*/
public Builder concurrencyLevel(int concurrencyLevel) {
if (this.concurrencyLevel != UNSET_CONCURRENCY_LEVEL) {
throw new IllegalStateException("concurrency level was already set to "
+ this.concurrencyLevel);
}
if (concurrencyLevel <= 0) {
throw new IllegalArgumentException();
}
this.concurrencyLevel = concurrencyLevel;
return this;
}
/**
* Creates a new concurrent hash map backed by the given strategy.
*
* @param strategy
* used to implement and manipulate the entries
* @param <K>
* the type of keys to be stored in the returned map
* @param <V>
* the type of values to be stored in the returned map
* @param <E>
* the type of internal entry to be stored in the returned map
* @throws NullPointerException
* if strategy is null
*/
public <K, V, E> ConcurrentMap<K, V> buildMap(Strategy<K, V, E> strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
return new Impl<K, V, E>(strategy, this);
}
/**
* Creates a {@link ConcurrentMap}, backed by the given strategy, that
* supports atomic, on-demand computation of values. {@link Map#get}
* returns the value corresponding to the given key, atomically computes
* it using the computer function passed to this builder, or waits for
* another thread to compute the value if necessary. Only one value will
* be computed for each key at a given time.
* <p>
* If an entry's value has not finished computing yet, query methods
* besides {@link java.util.Map#get} return immediately as if an entry
* doesn't exist. In other words, an entry isn't externally visible
* until the value's computation completes.
* <p>
* {@link Map#get} in the returned map implementation throws:
* <ul>
* <li>{@link NullPointerException} if the key is null or the computer
* returns null</li>
* <li>or {@link ComputationException} wrapping an exception thrown by
* the computation</li>
* </ul>
* <p>
* <b>Note:</b> Callers of {@code get()} <i>must</i> ensure that the key
* argument is of type {@code K}. {@code Map.get()} takes {@code Object}
* , so the key type is not checked at compile time. Passing an object
* of a type other than {@code K} can result in that object being
* unsafely passed to the computer function as type {@code K} not to
* mention the unsafe key being stored in the map.
*
* @param strategy
* used to implement and manipulate the entries
* @param computer
* used to compute values for keys
* @param <K>
* the type of keys to be stored in the returned map
* @param <V>
* the type of values to be stored in the returned map
* @param <E>
* the type of internal entry to be stored in the returned map
* @throws NullPointerException
* if strategy or computer is null
*/
public <K, V, E> ConcurrentMap<K, V> buildComputingMap(ComputingStrategy<K, V, E> strategy,
Function<? super K, ? extends V> computer) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
if (computer == null) {
throw new NullPointerException("computer");
}
return new ComputingImpl<K, V, E>(strategy, this, computer);
}
int getInitialCapacity() {
return (initialCapacity == UNSET_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY
: initialCapacity;
}
int getConcurrencyLevel() {
return (concurrencyLevel == UNSET_CONCURRENCY_LEVEL) ? DEFAULT_CONCURRENCY_LEVEL
: concurrencyLevel;
}
}
/**
* Implements behavior specific to the client's concurrent hash map
* implementation. Used by the framework to create new entries and perform
* operations on them.
* <p>
* Method parameters are never null unless otherwise specified.
* <h3>Partially Reclaimed Entries</h3>
* <p>
* This class does <i>not</i> allow {@code null} to be used as a key.
* Setting values to null is not permitted, but entries may have null keys
* or values for various reasons. For example, the key or value may have
* been garbage collected or reclaimed through other means.
* CustomConcurrentHashMap treats entries with null keys and values as
* "partially reclaimed" and ignores them for the most part. Entries may
* enter a partially reclaimed state but they must not leave it. Partially
* reclaimed entries will not be copied over during table expansions, for
* example. Strategy implementations should proactively remove partially
* reclaimed entries so that {@link Map#isEmpty} and {@link Map#size()}
* return up-to-date results.
*
* @param <K>
* the type of keys to be stored in the returned map
* @param <V>
* the type of values to be stored in the returned map
* @param <E>
* internal entry type, not directly exposed to clients in map views
*/
public interface Strategy<K, V, E> {
/**
* Constructs a new entry for the given key with a pointer to the given
* next entry.
* <p>
* This method may return different entry implementations depending upon
* whether next is null or not. For example, if next is null (as will
* often be the case), this factory might use an entry class that
* doesn't waste memory on an unnecessary field.
*
* @param key
* for this entry
* @param hash
* of key returned by {@link #hashKey}
* @param next
* entry (used when implementing a hash bucket as a linked list,
* for example), possibly null
* @return a new entry
*/
abstract E newEntry(K key, int hash, E next);
/**
* Creates a copy of the given entry pointing to the given next entry.
* Copies the value and any other implementation-specific state from
* {@code original} to the returned entry. For example,
* CustomConcurrentHashMap might use this method when removing other
* entries or expanding the internal table.
*
* @param key
* for {@code original} as well as the returned entry. Explicitly
* provided here to prevent reclamation of the key at an
* inopportune time in implementations that don't otherwise keep
* a strong reference to the key.
* @param original
* entry from which the value and other implementation-specific
* state should be copied
* @param newNext
* the next entry the new entry should point to, possibly null
*/
E copyEntry(K key, E original, E newNext);
/**
* Sets the value of an entry using volatile semantics. Values are set
* synchronously on a per-entry basis.
*
* @param entry
* to set the value on
* @param value
* to set
*/
void setValue(E entry, V value);
/**
* Gets the value of an entry using volatile semantics.
*
* @param entry
* to get the value from
*/
V getValue(E entry);
/**
* Returns true if the two given keys are equal, false otherwise.
* Neither key will be null.
*
* @param a
* key from inside the map
* @param b
* key passed from caller, not necesarily of type K
* @see Object#equals the same contractual obligations apply here
*/
boolean equalKeys(K a, Object b);
/**
* Returns true if the two given values are equal, false otherwise.
* Neither value will be null.
*
* @param a
* value from inside the map
* @param b
* value passed from caller, not necesarily of type V
* @see Object#equals the same contractual obligations apply here
*/
boolean equalValues(V a, Object b);
/**
* Returns a hash code for the given key.
*
* @see Object#hashCode the same contractual obligations apply here
*/
int hashKey(Object key);
/**
* Gets the key for the given entry. This may return null (for example,
* if the key was reclaimed by the garbage collector).
*
* @param entry
* to get key from
* @return key from the given entry
*/
K getKey(E entry);
/**
* Gets the next entry relative to the given entry, the exact same entry
* that was provided to {@link Strategy#newEntry} when the given entry
* was created.
*
* @return the next entry or null if null was passed to
* {@link Strategy#newEntry}
*/
E getNext(E entry);
/**
* Returns the hash code that was passed to {@link Strategy#newEntry})
* when the given entry was created.
*/
int getHash(E entry);
// TODO:
// /**
// * Notifies the strategy that an entry has been removed from the map.
// *
// * @param entry that was recently removed
// */
// void remove(E entry);
/**
* Provides an API for interacting directly with the map's internal
* entries to this strategy. Invoked once when the map is created.
* Strategies that don't need access to the map's internal entries can
* simply ignore the argument.
*
* @param internals
* of the map, enables direct interaction with the internal
* entries
*/
void setInternals(Internals<K, V, E> internals);
}
/**
* Provides access to a map's internal entries.
*/
public interface Internals<K, V, E> {
// TODO:
// /**
// * Returns a set view of the internal entries.
// */
// Set<E> entrySet();
/**
* Returns the internal entry corresponding to the given key from the
* map.
*
* @param key
* to retrieve entry for
* @throws NullPointerException
* if key is null
*/
E getEntry(K key);
/**
* Removes the given entry from the map if the value of the entry in the
* map matches the given value.
*
* @param entry
* to remove
* @param value
* entry must have for the removal to succeed
* @throws NullPointerException
* if entry is null
*/
boolean removeEntry(E entry, V value);
/**
* Removes the given entry from the map.
*
* @param entry
* to remove
* @throws NullPointerException
* if entry is null
*/
boolean removeEntry(E entry);
}
/**
* Extends {@link Strategy} to add support for computing values on-demand.
* Implementations should typically intialize the entry's value to a
* placeholder value in {@link #newEntry(Object, int, Object)} so that
* {@link #waitForValue(Object)} can tell the difference between a
* pre-intialized value and an in-progress computation.
* {@link #copyEntry(Object, Object, Object)} must detect and handle the
* case where an entry is copied in the middle of a computation.
* Implementations can throw {@link UnsupportedOperationException} in
* {@link #setValue(Object, Object)} if they wish to prevent users from
* setting values directly.
*
* @see Builder#buildComputingMap(CustomConcurrentHashMap.ComputingStrategy,
* Function)
*/
public interface ComputingStrategy<K, V, E> extends Strategy<K, V, E> {
/**
* Computes a value for the given key and stores it in the given entry.
* Called as a result of {@link Map#get}. If this method throws an
* exception, CustomConcurrentHashMap will remove the entry and retry
* the computation on subsequent requests.
*
* @param entry
* that was created
* @param computer
* passed to {@link Builder#buildMap}
* @throws ComputationException
* if the computation threw an exception
* @throws NullPointerException
* if the computer returned null
* @return the computed value
*/
V compute(K key, E entry, Function<? super K, ? extends V> computer);
/**
* Gets a value from an entry waiting for the value to be set by
* {@link #compute} if necessary. Returns null if a value isn't
* available at which point CustomConcurrentHashMap tries to compute a
* new value.
*
* @param entry
* to return value from
* @return stored value or null if the value isn't available
* @throws InterruptedException
* if the thread was interrupted while waiting
*/
V waitForValue(E entry) throws InterruptedException;
}
/**
* Applies a supplemental hash function to a given hash code, which defends
* against poor quality hash functions. This is critical when the concurrent
* hash map uses power-of-two length hash tables, that otherwise encounter
* collisions for hash codes that do not differ in lower or upper bits.
*
* @param h
* hash code
*/
private static int rehash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
/** The concurrent hash map implementation. */
static class Impl<K, V, E> extends AbstractMap<K, V> implements ConcurrentMap<K, V>,
Serializable {
/*
* The basic strategy is to subdivide the table among Segments, each of
* which itself is a concurrently readable hash table.
*/
/* ---------------- Constants -------------- */
/**
* 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 to ensure that entries are indexable using ints.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The maximum number of segments to allow; used to bound constructor
* arguments.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of unsynchronized retries in size and containsValue methods
* before resorting to locking. This is used to avoid unbounded retries
* if tables undergo continuous modification which would make it
* impossible to obtain an accurate result.
*/
static final int RETRIES_BEFORE_LOCK = 2;
/* ---------------- Fields -------------- */
/**
* The strategy used to implement this map.
*/
final Strategy<K, V, E> strategy;
/**
* Mask value for indexing into segments. The upper bits of a key's hash
* code are used to choose the segment.
*/
final int segmentMask;
/**
* Shift value for indexing within segments. Helps prevent entries that
* end up in the same segment from also ending up in the same bucket.
*/
final int segmentShift;
/**
* The segments, each of which is a specialized hash table
*/
final Segment[] segments;
/**
* Creates a new, empty map with the specified strategy, initial
* capacity, load factor and concurrency level.
*/
Impl(Strategy<K, V, E> strategy, Builder builder) {
int concurrencyLevel = builder.getConcurrencyLevel();
int initialCapacity = builder.getInitialCapacity();
if (concurrencyLevel > MAX_SEGMENTS) {
concurrencyLevel = MAX_SEGMENTS;
}
// Find power-of-two sizes best matching arguments
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel) {
++segmentShift;
segmentCount <<= 1;
}
this.segmentShift = 32 - segmentShift;
segmentMask = segmentCount - 1;
this.segments = newSegmentArray(segmentCount);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
int segmentCapacity = initialCapacity / segmentCount;
if (segmentCapacity * segmentCount < initialCapacity) {
++segmentCapacity;
}
int segmentSize = 1;
while (segmentSize < segmentCapacity) {
segmentSize <<= 1;
}
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] = new Segment(segmentSize);
}
this.strategy = strategy;
strategy.setInternals(new InternalsImpl());
}
int hash(Object key) {
int h = strategy.hashKey(key);
return rehash(h);
}
class InternalsImpl implements Internals<K, V, E>, Serializable {
static final long serialVersionUID = 0;
public E getEntry(K key) {
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
return segmentFor(hash).getEntry(key, hash);
}
public boolean removeEntry(E entry, V value) {
if (entry == null) {
throw new NullPointerException("entry");
}
int hash = strategy.getHash(entry);
return segmentFor(hash).removeEntry(entry, hash, value);
}
public boolean removeEntry(E entry) {
if (entry == null) {
throw new NullPointerException("entry");
}
int hash = strategy.getHash(entry);
return segmentFor(hash).removeEntry(entry, hash);
}
}
Segment[] newSegmentArray(int ssize) {
// Note: This is the only way I could figure out how to create
// a segment array (the compile has a tough time with arrays of
// inner classes of generic types apparently). Safe because we're
// restricting what can go in the array and no one has an
// unrestricted reference.
return (Segment[]) Array.newInstance(Segment.class, ssize);
}
/* ---------------- Small Utilities -------------- */
/**
* Returns the segment that should be used for key with given hash
*
* @param hash
* the hash code for the key
* @return the segment
*/
Segment segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
/* ---------------- Inner Classes -------------- */
/**
* Segments are specialized versions of hash tables. This subclasses
* from ReentrantLock opportunistically, just to simplify some locking
* and avoid separate construction.
*/
@SuppressWarnings("serial")
// This class is never serialized.
final class Segment extends ReentrantLock {
/*
* 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. - 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.
*/
/**
* The number of elements in this segment's region.
*/
volatile int count;
/**
* 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.
*/
int modCount;
/**
* The table is expanded when its size exceeds this threshold. (The
* value of this field is always {@code (int)(capacity *
* loadFactor)}.)
*/
int threshold;
/**
* The per-segment table.
*/
volatile AtomicReferenceArray<E> table;
Segment(int initialCapacity) {
setTable(newEntryArray(initialCapacity));
}
AtomicReferenceArray<E> newEntryArray(int size) {
return new AtomicReferenceArray<E>(size);
}
/**
* Sets table to new HashEntry array. Call only while holding lock
* or in constructor.
*/
void setTable(AtomicReferenceArray<E> newTable) {
this.threshold = newTable.length() * 3 / 4;
this.table = newTable;
}
/**
* Returns properly casted first entry of bin for given hash.
*/
E getFirst(int hash) {
AtomicReferenceArray<E> table = this.table;
return table.get(hash & (table.length() - 1));
}
/* Specialized implementations of map methods */
public E getEntry(Object key, int hash) {
Strategy<K, V, E> s = Impl.this.strategy;
if (count != 0) { // read-volatile
for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
if (s.getHash(e) != hash) {
continue;
}
K entryKey = s.getKey(e);
if (entryKey == null) {
continue;
}
if (s.equalKeys(entryKey, key)) {
return e;
}
}
}
return null;
}
V get(Object key, int hash) {
E entry = getEntry(key, hash);
if (entry == null) {
return null;
}
return strategy.getValue(entry);
}
boolean containsKey(Object key, int hash) {
Strategy<K, V, E> s = Impl.this.strategy;
if (count != 0) { // read-volatile
for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
if (s.getHash(e) != hash) {
continue;
}
K entryKey = s.getKey(e);
if (entryKey == null) {
continue;
}
if (s.equalKeys(entryKey, key)) {
// Return true only if this entry has a value.
return s.getValue(e) != null;
}
}
}
return false;
}
boolean containsValue(Object value) {
Strategy<K, V, E> s = Impl.this.strategy;
if (count != 0) { // read-volatile
AtomicReferenceArray<E> table = this.table;
int length = table.length();
for (int i = 0; i < length; i++) {
for (E e = table.get(i); e != null; e = s.getNext(e)) {
V entryValue = s.getValue(e);
// If the value disappeared, this entry is partially
// collected,
// and we should skip it.
if (entryValue == null) {
continue;
}
if (s.equalValues(entryValue, value)) {
return true;
}
}
}
}
return false;
}
boolean replace(K key, int hash, V oldValue, V newValue) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
K entryKey = s.getKey(e);
if (s.getHash(e) == hash && entryKey != null && s.equalKeys(key, entryKey)) {
// If the value disappeared, this entry is partially
// collected,
// and we should pretend like it doesn't exist.
V entryValue = s.getValue(e);
if (entryValue == null) {
return false;
}
if (s.equalValues(entryValue, oldValue)) {
s.setValue(e, newValue);
return true;
}
}
}
return false;
} finally {
unlock();
}
}
V replace(K key, int hash, V newValue) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
for (E e = getFirst(hash); e != null; e = s.getNext(e)) {
K entryKey = s.getKey(e);
if (s.getHash(e) == hash && entryKey != null && s.equalKeys(key, entryKey)) {
// If the value disappeared, this entry is partially
// collected,
// and we should pretend like it doesn't exist.
V entryValue = s.getValue(e);
if (entryValue == null) {
return null;
}
s.setValue(e, newValue);
return entryValue;
}
}
return null;
} finally {
unlock();
}
}
V put(K key, int hash, V value, boolean onlyIfAbsent) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
int count = this.count;
if (count++ > this.threshold) { // ensure capacity
expand();
}
AtomicReferenceArray<E> table = this.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
// Look for an existing entry.
for (E e = first; e != null; e = s.getNext(e)) {
K entryKey = s.getKey(e);
if (s.getHash(e) == hash && entryKey != null && s.equalKeys(key, entryKey)) {
// We found an existing entry.
// If the value disappeared, this entry is partially
// collected,
// and we should pretend like it doesn't exist.
V entryValue = s.getValue(e);
if (onlyIfAbsent && entryValue != null) {
return entryValue;
}
s.setValue(e, value);
return entryValue;
}
}
// Create a new entry.
++modCount;
E newEntry = s.newEntry(key, hash, first);
s.setValue(newEntry, value);
table.set(index, newEntry);
this.count = count; // write-volatile
return null;
} finally {
unlock();
}
}
/**
* Expands the table if possible.
*/
void expand() {
AtomicReferenceArray<E> oldTable = table;
int oldCapacity = oldTable.length();
if (oldCapacity >= MAXIMUM_CAPACITY) {
return;
}
/*
* 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.
*/
Strategy<K, V, E> s = Impl.this.strategy;
AtomicReferenceArray<E> newTable = newEntryArray(oldCapacity << 1);
threshold = newTable.length() * 3 / 4;
int newMask = newTable.length() - 1;
for (int oldIndex = 0; oldIndex < oldCapacity; oldIndex++) {
// We need to guarantee that any existing reads of old Map
// can
// proceed. So we cannot yet null out each bin.
E head = oldTable.get(oldIndex);
if (head != null) {
E next = s.getNext(head);
int headIndex = s.getHash(head) & newMask;
// Single node on list
if (next == null) {
newTable.set(headIndex, head);
} else {
// Reuse the consecutive sequence of nodes with the
// same target
// index from the end of the list. tail points to
// the first
// entry in the reusable list.
E tail = head;
int tailIndex = headIndex;
for (E last = next; last != null; last = s.getNext(last)) {
int newIndex = s.getHash(last) & newMask;
if (newIndex != tailIndex) {
// The index changed. We'll need to copy the
// previous entry.
tailIndex = newIndex;
tail = last;
}
}
newTable.set(tailIndex, tail);
// Clone nodes leading up to the tail.
for (E e = head; e != tail; e = s.getNext(e)) {
K key = s.getKey(e);
if (key != null) {
int newIndex = s.getHash(e) & newMask;
E newNext = newTable.get(newIndex);
newTable.set(newIndex, s.copyEntry(key, e, newNext));
} else {
// Key was reclaimed. Skip entry.
}
}
}
}
}
table = newTable;
}
V remove(Object key, int hash) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray<E> table = this.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
for (E e = first; e != null; e = s.getNext(e)) {
K entryKey = s.getKey(e);
if (s.getHash(e) == hash && entryKey != null && s.equalKeys(entryKey, key)) {
V entryValue = strategy.getValue(e);
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
E newFirst = s.getNext(e);
for (E p = first; p != e; p = s.getNext(p)) {
K pKey = s.getKey(p);
if (pKey != null) {
newFirst = s.copyEntry(pKey, p, newFirst);
} else {
// Key was reclaimed. Skip entry.
}
}
table.set(index, newFirst);
this.count = count; // write-volatile
return entryValue;
}
}
return null;
} finally {
unlock();
}
}
boolean remove(Object key, int hash, Object value) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray<E> table = this.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
for (E e = first; e != null; e = s.getNext(e)) {
K entryKey = s.getKey(e);
if (s.getHash(e) == hash && entryKey != null && s.equalKeys(entryKey, key)) {
V entryValue = strategy.getValue(e);
if (value == entryValue
|| (value != null && entryValue != null && s.equalValues(
entryValue, value))) {
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
E newFirst = s.getNext(e);
for (E p = first; p != e; p = s.getNext(p)) {
K pKey = s.getKey(p);
if (pKey != null) {
newFirst = s.copyEntry(pKey, p, newFirst);
} else {
// Key was reclaimed. Skip entry.
}
}
table.set(index, newFirst);
this.count = count; // write-volatile
return true;
} else {
return false;
}
}
}
return false;
} finally {
unlock();
}
}
public boolean removeEntry(E entry, int hash, V value) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray<E> table = this.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
for (E e = first; e != null; e = s.getNext(e)) {
if (s.getHash(e) == hash && entry.equals(e)) {
V entryValue = s.getValue(e);
if (entryValue == value
|| (value != null && s.equalValues(entryValue, value))) {
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
E newFirst = s.getNext(e);
for (E p = first; p != e; p = s.getNext(p)) {
K pKey = s.getKey(p);
if (pKey != null) {
newFirst = s.copyEntry(pKey, p, newFirst);
} else {
// Key was reclaimed. Skip entry.
}
}
table.set(index, newFirst);
this.count = count; // write-volatile
return true;
} else {
return false;
}
}
}
return false;
} finally {
unlock();
}
}
public boolean removeEntry(E entry, int hash) {
Strategy<K, V, E> s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray<E> table = this.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
for (E e = first; e != null; e = s.getNext(e)) {
if (s.getHash(e) == hash && entry.equals(e)) {
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
E newFirst = s.getNext(e);
for (E p = first; p != e; p = s.getNext(p)) {
K pKey = s.getKey(p);
if (pKey != null) {
newFirst = s.copyEntry(pKey, p, newFirst);
} else {
// Key was reclaimed. Skip entry.
}
}
table.set(index, newFirst);
this.count = count; // write-volatile
return true;
}
}
return false;
} finally {
unlock();
}
}
void clear() {
if (count != 0) {
lock();
try {
AtomicReferenceArray<E> table = this.table;
for (int i = 0; i < table.length(); i++) {
table.set(i, null);
}
++modCount;
count = 0; // write-volatile
} finally {
unlock();
}
}
}
}
/* ---------------- Public operations -------------- */
/**
* Returns {@code true} if this map contains no key-value mappings.
*
* @return {@code true} if this map contains no key-value mappings
*/
@Override
public boolean isEmpty() {
final Segment[] 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.
*/
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) {
return false;
}
}
}
return true;
}
/**
* Returns the number of key-value mappings in this map. If the map
* contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
final Segment[] 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;
}
}
}
if (check == sum) {
break;
}
}
if (check != sum) { // Resort to locking all segments
sum = 0;
for (Segment segment : segments) {
segment.lock();
}
for (Segment segment : segments) {
sum += segment.count;
}
for (Segment segment : segments) {
segment.unlock();
}
}
if (sum > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) sum;
}
}
/**
* Returns the value to which the specified key is mapped, or {@code
* null} if this map contains no mapping for the key.
* <p>
* More formally, if this map contains a mapping from a key {@code k} to
* a value {@code v} such that {@code key.equals(k)}, then this method
* returns {@code v}; otherwise it returns {@code null}. (There can be
* at most one such mapping.)
*
* @throws NullPointerException
* if the specified key is null
*/
@Override
public V get(Object key) {
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
return segmentFor(hash).get(key, hash);
}
/**
* Tests if the specified object is a key in this table.
*
* @param key
* possible key
* @return {@code true} if and only if the specified object is a key in
* this table, as determined by the {@code equals} method;
* {@code false} otherwise.
* @throws NullPointerException
* if the specified key is null
*/
@Override
public boolean containsKey(Object key) {
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
return segmentFor(hash).containsKey(key, hash);
}
/**
* Returns {@code true} 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 {@code
* containsKey}.
*
* @param value
* value whose presence in this map is to be tested
* @return {@code true} if this map maps one or more keys to the
* specified value
* @throws NullPointerException
* if the specified value is null
*/
@Override
public boolean containsValue(Object value) {
if (value == null) {
throw new NullPointerException("value");
}
// See explanation of modCount use above
final Segment[] 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 mcsum = 0;
for (int i = 0; i < segments.length; ++i) {
@SuppressWarnings("UnusedDeclaration")
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) {
@SuppressWarnings("UnusedDeclaration")
int c = segments[i].count;
if (mc[i] != segments[i].modCount) {
cleanSweep = false;
break;
}
}
}
if (cleanSweep) {
return false;
}
}
// Resort to locking all segments
for (Segment segment : segments) {
segment.lock();
}
boolean found = false;
try {
for (Segment segment : segments) {
if (segment.containsValue(value)) {
found = true;
break;
}
}
} finally {
for (Segment segment : segments) {
segment.unlock();
}
}
return found;
}
/**
* Maps the specified key to the specified value in this table. Neither
* the key nor the value can be null.
* <p>
* The value can be retrieved by calling the {@code get} method with a
* key that is equal to the original key.
*
* @param key
* key with which the specified value is to be associated
* @param value
* value to be associated with the specified key
* @return the previous value associated with {@code key}, or {@code
* null} if there was no mapping for {@code key}
* @throws NullPointerException
* if the specified key or value is null
*/
@Override
public V put(K key, V value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, false);
}
/**
* {@inheritDoc}
*
* @return the previous value associated with the specified key, or
* {@code null} if there was no mapping for the key
* @throws NullPointerException
* if the specified key or value is null
*/
public V putIfAbsent(K key, V value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, true);
}
/**
* 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 m
* mappings to be stored in this map
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Removes the key (and its corresponding value) from this map. This
* method does nothing if the key is not in the map.
*
* @param key
* the key that needs to be removed
* @return the previous value associated with {@code key}, or {@code
* null} if there was no mapping for {@code key}
* @throws NullPointerException
* if the specified key is null
*/
@Override
public V remove(Object key) {
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
return segmentFor(hash).remove(key, hash);
}
/**
* {@inheritDoc}
*
* @throws NullPointerException
* if the specified key is null
*/
public boolean remove(Object key, Object value) {
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
return segmentFor(hash).remove(key, hash, value);
}
/**
* {@inheritDoc}
*
* @throws NullPointerException
* if any of the arguments are null
*/
public boolean replace(K key, V oldValue, V newValue) {
if (key == null) {
throw new NullPointerException("key");
}
if (oldValue == null) {
throw new NullPointerException("oldValue");
}
if (newValue == null) {
throw new NullPointerException("newValue");
}
int hash = hash(key);
return segmentFor(hash).replace(key, hash, oldValue, newValue);
}
/**
* {@inheritDoc}
*
* @return the previous value associated with the specified key, or
* {@code null} if there was no mapping for the key
* @throws NullPointerException
* if the specified key or value is null
*/
public V replace(K key, V value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
int hash = hash(key);
return segmentFor(hash).replace(key, hash, value);
}
/**
* Removes all of the mappings from this map.
*/
@Override
public void clear() {
for (Segment segment : segments) {
segment.clear();
}
}
Set<K> keySet;
/**
* Returns a {@link java.util.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 {@code Iterator.remove}, {@code Set.remove}, {@code removeAll},
* {@code retainAll}, and {@code clear} operations. It does not support
* the {@code add} or {@code addAll} operations.
* <p>
* The view's {@code iterator} is a "weakly consistent" iterator that
* will never throw {@link java.util.ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon construction
* of the iterator, and may (but is not guaranteed to) reflect any
* modifications subsequent to construction.
*/
@Override
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
Collection<V> values;
/**
* Returns a {@link java.util.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 {@code Iterator.remove}, {@code
* Collection.remove}, {@code removeAll}, {@code retainAll}, and {@code
* clear} operations. It does not support the {@code add} or {@code
* addAll} operations.
* <p>
* The view's {@code iterator} is a "weakly consistent" iterator that
* will never throw {@link java.util.ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon construction
* of the iterator, and may (but is not guaranteed to) reflect any
* modifications subsequent to construction.
*/
@Override
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null) ? vs : (values = new Values());
}
Set<Entry<K, V>> entrySet;
/**
* Returns a {@link java.util.Set} view of the mappings 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 the map, via
* the {@code Iterator.remove}, {@code Set.remove}, {@code removeAll},
* {@code retainAll}, and {@code clear} operations. It does not support
* the {@code add} or {@code addAll} operations.
* <p>
* The view's {@code iterator} is a "weakly consistent" iterator that
* will never throw {@link java.util.ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon construction
* of the iterator, and may (but is not guaranteed to) reflect any
* modifications subsequent to construction.
*/
@Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
/* ---------------- Iterator Support -------------- */
abstract class HashIterator {
int nextSegmentIndex;
int nextTableIndex;
AtomicReferenceArray<E> currentTable;
E nextEntry;
WriteThroughEntry nextExternal;
WriteThroughEntry lastReturned;
HashIterator() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
advance();
}
public boolean hasMoreElements() {
return hasNext();
}
final void advance() {
nextExternal = null;
if (nextInChain()) {
return;
}
if (nextInTable()) {
return;
}
while (nextSegmentIndex >= 0) {
Segment seg = segments[nextSegmentIndex--];
if (seg.count != 0) {
currentTable = seg.table;
nextTableIndex = currentTable.length() - 1;
if (nextInTable()) {
return;
}
}
}
}
/**
* Finds the next entry in the current chain. Returns true if an
* entry was found.
*/
boolean nextInChain() {
Strategy<K, V, E> s = Impl.this.strategy;
if (nextEntry != null) {
for (nextEntry = s.getNext(nextEntry); nextEntry != null; nextEntry = s.getNext(nextEntry)) {
if (advanceTo(nextEntry)) {
return true;
}
}
}
return false;
}
/**
* Finds the next entry in the current table. Returns true if an
* entry was found.
*/
boolean nextInTable() {
while (nextTableIndex >= 0) {
if ((nextEntry = currentTable.get(nextTableIndex--)) != null) {
if (advanceTo(nextEntry) || nextInChain()) {
return true;
}
}
}
return false;
}
/**
* Advances to the given entry. Returns true if the entry was valid,
* false if it should be skipped.
*/
boolean advanceTo(E entry) {
Strategy<K, V, E> s = Impl.this.strategy;
K key = s.getKey(entry);
V value = s.getValue(entry);
if (key != null && value != null) {
nextExternal = new WriteThroughEntry(key, value);
return true;
} else {
// Skip partially reclaimed entry.
return false;
}
}
public boolean hasNext() {
return nextExternal != null;
}
WriteThroughEntry nextEntry() {
if (nextExternal == null) {
throw new NoSuchElementException();
}
lastReturned = nextExternal;
advance();
return lastReturned;
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
Impl.this.remove(lastReturned.getKey());
lastReturned = null;
}
}
final class KeyIterator extends HashIterator implements Iterator<K> {
public K next() {
return super.nextEntry().getKey();
}
}
final class ValueIterator extends HashIterator implements Iterator<V> {
public V next() {
return super.nextEntry().getValue();
}
}
/**
* Custom Entry class used by EntryIterator.next(), that relays setValue
* changes to the underlying map.
*/
final class WriteThroughEntry extends AbstractMapEntry<K, V> {
final K key;
V value;
WriteThroughEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
/**
* Set our entry's value and write 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 different from what we return (or could
* even have been removed in which case the put will re-establish).
* We do not and cannot guarantee more.
*/
@Override
public V setValue(V value) {
if (value == null) {
throw new NullPointerException();
}
V oldValue = Impl.this.put(getKey(), value);
this.value = value;
return oldValue;
}
}
final class EntryIterator extends HashIterator implements Iterator<Entry<K, V>> {
public Entry<K, V> next() {
return nextEntry();
}
}
final class KeySet extends AbstractSet<K> {
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return Impl.this.size();
}
@Override
public boolean isEmpty() {
return Impl.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return Impl.this.containsKey(o);
}
@Override
public boolean remove(Object o) {
return Impl.this.remove(o) != null;
}
@Override
public void clear() {
Impl.this.clear();
}
}
final class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return Impl.this.size();
}
@Override
public boolean isEmpty() {
return Impl.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return Impl.this.containsValue(o);
}
@Override
public void clear() {
Impl.this.clear();
}
}
final class EntrySet extends AbstractSet<Entry<K, V>> {
@Override
public Iterator<Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<?, ?> e = (Entry<?, ?>) o;
Object key = e.getKey();
if (key == null) {
return false;
}
V v = Impl.this.get(key);
return v != null && strategy.equalValues(v, e.getValue());
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<?, ?> e = (Entry<?, ?>) o;
Object key = e.getKey();
return key != null && Impl.this.remove(key, e.getValue());
}
@Override
public int size() {
return Impl.this.size();
}
@Override
public boolean isEmpty() {
return Impl.this.isEmpty();
}
@Override
public void clear() {
Impl.this.clear();
}
}
/* ---------------- Serialization Support -------------- */
private static final long serialVersionUID = 1;
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.writeInt(size());
out.writeInt(segments.length); // concurrencyLevel
out.writeObject(strategy);
for (Entry<K, V> entry : entrySet()) {
out.writeObject(entry.getKey());
out.writeObject(entry.getValue());
}
out.writeObject(null); // terminate entries
}
/**
* Fields used during deserialization. We use a nested class so we don't
* load them until we need them. We need to use reflection to set final
* fields outside of the constructor.
*/
static class Fields {
static final Field segmentShift = findField("segmentShift");
static final Field segmentMask = findField("segmentMask");
static final Field segments = findField("segments");
static final Field strategy = findField("strategy");
static Field findField(String name) {
try {
Field f = Impl.class.getDeclaredField(name);
f.setAccessible(true);
return f;
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
}
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
try {
int initialCapacity = in.readInt();
int concurrencyLevel = in.readInt();
Strategy<K, V, E> strategy = (Strategy<K, V, E>) in.readObject();
if (concurrencyLevel > MAX_SEGMENTS) {
concurrencyLevel = MAX_SEGMENTS;
}
// Find power-of-two sizes best matching arguments
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel) {
++segmentShift;
segmentCount <<= 1;
}
Fields.segmentShift.set(this, 32 - segmentShift);
Fields.segmentMask.set(this, segmentCount - 1);
Fields.segments.set(this, newSegmentArray(segmentCount));
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
int segmentCapacity = initialCapacity / segmentCount;
if (segmentCapacity * segmentCount < initialCapacity) {
++segmentCapacity;
}
int segmentSize = 1;
while (segmentSize < segmentCapacity) {
segmentSize <<= 1;
}
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] = new Segment(segmentSize);
}
Fields.strategy.set(this, strategy);
while (true) {
K key = (K) in.readObject();
if (key == null) {
break; // terminator
}
V value = (V) in.readObject();
put(key, value);
}
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
static class ComputingImpl<K, V, E> extends Impl<K, V, E> {
static final long serialVersionUID = 0;
final ComputingStrategy<K, V, E> computingStrategy;
final Function<? super K, ? extends V> computer;
/**
* Creates a new, empty map with the specified strategy, initial
* capacity, load factor and concurrency level.
*/
ComputingImpl(ComputingStrategy<K, V, E> strategy, Builder builder,
Function<? super K, ? extends V> computer) {
super(strategy, builder);
this.computingStrategy = strategy;
this.computer = computer;
}
@Override
public V get(Object k) {
/*
* This cast isn't safe, but we can rely on the fact that K is
* almost always passed to Map.get(), and tools like IDEs and
* Findbugs can catch situations where this isn't the case. The
* alternative is to add an overloaded method, but the chances of a
* user calling get() instead of the new API and the risks inherent
* in adding a new API outweigh this little hole.
*/
@SuppressWarnings("unchecked")
K key = (K) k;
if (key == null) {
throw new NullPointerException("key");
}
int hash = hash(key);
Segment segment = segmentFor(hash);
outer: while (true) {
E entry = segment.getEntry(key, hash);
if (entry == null) {
boolean created = false;
segment.lock();
try {
// Try again--an entry could have materialized in the
// interim.
entry = segment.getEntry(key, hash);
if (entry == null) {
// Create a new entry.
created = true;
int count = segment.count;
if (count++ > segment.threshold) { // ensure
// capacity
segment.expand();
}
AtomicReferenceArray<E> table = segment.table;
int index = hash & (table.length() - 1);
E first = table.get(index);
++segment.modCount;
entry = computingStrategy.newEntry(key, hash, first);
table.set(index, entry);
segment.count = count; // write-volatile
}
} finally {
segment.unlock();
}
if (created) {
// This thread solely created the entry.
boolean success = false;
try {
V value = computingStrategy.compute(key, entry, computer);
if (value == null) {
throw new NullPointerException(
"compute() returned null unexpectedly");
}
success = true;
return value;
} finally {
if (!success) {
segment.removeEntry(entry, hash);
}
}
}
}
// The entry already exists. Wait for the computation.
boolean interrupted = false;
try {
while (true) {
try {
V value = computingStrategy.waitForValue(entry);
if (value == null) {
// Purge entry and try again.
segment.removeEntry(entry, hash);
continue outer;
}
return value;
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
}
/**
* A basic, no-frills implementation of {@code Strategy} using
* {@link SimpleInternalEntry}. Intended to be subclassed to provide
* customized behavior. For example, the following creates a map that uses
* byte arrays as keys:
*
* <pre>
* @code
* return new CustomConcurrentHashMap.Builder().buildMap(
* new SimpleStrategy<byte[], V>() {
* public int hashKey(Object key) {
* return Arrays.hashCode((byte[]) key);
* }
* public boolean equalKeys(byte[] a, Object b) {
* return Arrays.equals(a, (byte[]) b);
* }
* });}
* </pre>
*
* With nothing overridden, it yields map behavior equivalent to that of
* {@link ConcurrentHashMap}.
*/
static class SimpleStrategy<K, V> implements Strategy<K, V, SimpleInternalEntry<K, V>> {
public SimpleInternalEntry<K, V> newEntry(K key, int hash, SimpleInternalEntry<K, V> next) {
return new SimpleInternalEntry<K, V>(key, hash, null, next);
}
public SimpleInternalEntry<K, V> copyEntry(K key, SimpleInternalEntry<K, V> original,
SimpleInternalEntry<K, V> next) {
return new SimpleInternalEntry<K, V>(key, original.hash, original.value, next);
}
public void setValue(SimpleInternalEntry<K, V> entry, V value) {
entry.value = value;
}
public V getValue(SimpleInternalEntry<K, V> entry) {
return entry.value;
}
public boolean equalKeys(K a, Object b) {
return a.equals(b);
}
public boolean equalValues(V a, Object b) {
return a.equals(b);
}
public int hashKey(Object key) {
return key.hashCode();
}
public K getKey(SimpleInternalEntry<K, V> entry) {
return entry.key;
}
public SimpleInternalEntry<K, V> getNext(SimpleInternalEntry<K, V> entry) {
return entry.next;
}
public int getHash(SimpleInternalEntry<K, V> entry) {
return entry.hash;
}
public void setInternals(Internals<K, V, SimpleInternalEntry<K, V>> internals) {
// ignore?
}
}
/**
* A basic, no-frills entry class used by {@link SimpleInternalEntry}.
*/
static class SimpleInternalEntry<K, V> {
final K key;
final int hash;
final SimpleInternalEntry<K, V> next;
volatile V value;
SimpleInternalEntry(K key, int hash, V value, SimpleInternalEntry<K, V> next) {
this.key = key;
this.hash = hash;
this.value = value;
this.next = next;
}
}
}