com.google.common.collect.CustomConcurrentHashMap Maven / Gradle / Ivy
Show all versions of google-collections Show documentation
/*
* 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.google.common.collect;
import com.google.common.base.Function;
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 javax.annotation.Nullable;
/**
* 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)}).
*
* 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 not entail locking,
* and there is not any support for locking the entire table
* in a way that prevents all access.
*
*
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 completed 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 not throw
* {@link java.util.ConcurrentModificationException}. However, iterators can
* only be used by one thread at a time.
*
*
The resulting {@link ConcurrentMap} and its views and iterators implement
* all of the optional methods of the {@link java.util.Map} and {@link
* java.util.Iterator} interfaces. Partially reclaimed entries are never
* exposed through the views or iterators.
*
*
For example, the following strategy emulates the behavior of
* {@link java.util.concurrent.ConcurrentHashMap}:
*
*
{@code
* class ConcurrentHashMapStrategy
* implements CustomConcurrentHashMap.Strategy>, Serializable {
* public InternalEntry newEntry(K key, int hash,
* InternalEntry next) {
* return new InternalEntry(key, hash, null, next);
* }
* public InternalEntry copyEntry(K key,
* InternalEntry original, InternalEntry next) {
* return new InternalEntry(key, original.hash, original.value, next);
* }
* public void setValue(InternalEntry entry, V value) {
* entry.value = value;
* }
* public V getValue(InternalEntry 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 entry) { return entry.key; }
* public InternalEntry getNext(InternalEntry entry) {
* return entry.next;
* }
* public int getHash(InternalEntry entry) { return entry.hash; }
* public void setInternals(CustomConcurrentHashMap.Internals> internals) {} // ignored
* }
*
* class InternalEntry {
* final K key;
* final int hash;
* volatile V value;
* final InternalEntry next;
* InternalEntry(K key, int hash, V value, InternalEntry next) {
* this.key = key;
* this.hash = hash;
* this.value = value;
* this.next = next;
* }
* }
* }
*
* To create a {@link java.util.concurrent.ConcurrentMap} using the strategy
* above:
*
* {@code
* ConcurrentMap map = new CustomConcurrentHashMap.Builder()
* .build(new ConcurrentHashMapStrategy());
* }
*
* @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 the type of keys to be stored in the returned map
* @param the type of values to be stored in the returned map
* @param the type of internal entry to be stored in the returned map
*
* @throws NullPointerException if strategy is null
*/
public ConcurrentMap buildMap(Strategy strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
return new Impl(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.
*
* 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.
*
*
{@link Map#get} in the returned map implementation throws:
*
* - {@link NullPointerException} if the key is null or the
* computer returns null
* - or {@link ComputationException} wrapping an exception thrown by the
* computation
*
*
* Note: Callers of {@code get()} must 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 the type of keys to be stored in the returned map
* @param the type of values to be stored in the returned map
* @param the type of internal entry to be stored in the returned map
*
* @throws NullPointerException if strategy or computer is null
*/
public ConcurrentMap buildComputingMap(
ComputingStrategy strategy,
Function super K, ? extends V> computer) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
if (computer == null) {
throw new NullPointerException("computer");
}
return new ComputingImpl(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.
*
* Method parameters are never null unless otherwise specified.
*
*
Partially Reclaimed Entries
*
* This class does not 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 the type of keys to be stored in the returned map
* @param the type of values to be stored in the returned map
* @param internal entry type, not directly exposed to clients in map
* views
*/
public interface Strategy {
/**
* Constructs a new entry for the given key with a pointer to the given
* next entry.
*
* 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 internals);
}
/**
* Provides access to a map's internal entries.
*/
public interface Internals {
// TODO:
// /**
// * Returns a set view of the internal entries.
// */
// Set 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, @Nullable 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 extends Strategy {
/**
* 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 extends AbstractMap
implements ConcurrentMap, 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 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 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, 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);
}
}
@SuppressWarnings("unchecked")
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 table;
Segment(int initialCapacity) {
setTable(newEntryArray(initialCapacity));
}
AtomicReferenceArray newEntryArray(int size) {
return new AtomicReferenceArray(size);
}
/**
* Sets table to new HashEntry array. Call only while holding lock or in
* constructor.
*/
void setTable(AtomicReferenceArray 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 table = this.table;
return table.get(hash & (table.length() - 1));
}
/* Specialized implementations of map methods */
public E getEntry(Object key, int hash) {
Strategy 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 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 s = Impl.this.strategy;
if (count != 0) { // read-volatile
AtomicReferenceArray 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 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 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 s = Impl.this.strategy;
lock();
try {
int count = this.count;
if (count++ > this.threshold) { // ensure capacity
expand();
}
AtomicReferenceArray 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 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 s = Impl.this.strategy;
AtomicReferenceArray 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 s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray 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 s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray 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 s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray 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 s = Impl.this.strategy;
lock();
try {
int count = this.count - 1;
AtomicReferenceArray 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 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.
*
* 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.
*
*
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 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.
*
* 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 keySet() {
Set ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
Collection 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.
*
* 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 values() {
Collection vs = values;
return (vs != null) ? vs : (values = new Values());
}
Set> 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.
*
* 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> entrySet() {
Set> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
/* ---------------- Iterator Support -------------- */
abstract class HashIterator {
int nextSegmentIndex;
int nextTableIndex;
AtomicReferenceArray 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 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 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 {
public K next() {
return super.nextEntry().getKey();
}
}
final class ValueIterator extends HashIterator implements Iterator {
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 {
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> {
public Entry next() {
return nextEntry();
}
}
final class KeySet extends AbstractSet {
@Override public Iterator 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 {
@Override public Iterator 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> {
@Override public Iterator> 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 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 strategy = (Strategy) 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 extends Impl {
static final long serialVersionUID = 0;
final ComputingStrategy 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 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 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: {@code
*
* return new CustomConcurrentHashMap.Builder().buildMap(
* new SimpleStrategy() {
* public int hashKey(Object key) {
* return Arrays.hashCode((byte[]) key);
* }
* public boolean equalKeys(byte[] a, Object b) {
* return Arrays.equals(a, (byte[]) b);
* }
* });}
*
* With nothing overridden, it yields map behavior equivalent to that of
* {@link ConcurrentHashMap}.
*/
static class SimpleStrategy
implements Strategy> {
public SimpleInternalEntry newEntry(
K key, int hash, SimpleInternalEntry next) {
return new SimpleInternalEntry(key, hash, null, next);
}
public SimpleInternalEntry copyEntry(K key,
SimpleInternalEntry original, SimpleInternalEntry next) {
return new SimpleInternalEntry(
key, original.hash, original.value, next);
}
public void setValue(SimpleInternalEntry entry, V value) {
entry.value = value;
}
public V getValue(SimpleInternalEntry 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 entry) {
return entry.key;
}
public SimpleInternalEntry getNext(SimpleInternalEntry entry) {
return entry.next;
}
public int getHash(SimpleInternalEntry entry) {
return entry.hash;
}
public void setInternals(
Internals> internals) {
// ignore?
}
}
/**
* A basic, no-frills entry class used by {@link SimpleInternalEntry}.
*/
static class SimpleInternalEntry {
final K key;
final int hash;
final SimpleInternalEntry next;
volatile V value;
SimpleInternalEntry(
K key, int hash, @Nullable V value, SimpleInternalEntry next) {
this.key = key;
this.hash = hash;
this.value = value;
this.next = next;
}
}
}