blob: 861945879d839d97d8a0c1e2d850f2c73e82fd74 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.util.containers;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
/**
* similar to java.util.ConcurrentHashMap except:
* conserved as much memory as possible by
* -- using only one Segment
* -- eliminating unnecessary fields
* -- using one of 256 ReentrantLock for Segment statically preallocated in {@link StripedReentrantLocks}
* added hashing strategy argument
* made not Serializable
*/
public class StripedLockConcurrentHashMap<K, V> extends _CHMSegment<K, V> implements ConcurrentMap<K, V> {
/* ---------------- Constants -------------- */
/**
* The default initial number of table slots for this table.
* Used when not otherwise specified in constructor.
*/
static int DEFAULT_INITIAL_CAPACITY = 16;
/**
* 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 indexible
* using ints.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The default load factor for this table. Used when not
* otherwise specified in constructor.
*/
public static final float DEFAULT_LOAD_FACTOR = 0.75f;
/* ---------------- Public operations -------------- */
public static <K,V> StripedLockConcurrentHashMap<K,V> createWithStrategy(@NotNull final TObjectHashingStrategy<K> hashingStrategy, int initialCapacity) {
return new StripedLockConcurrentHashMap<K, V>(initialCapacity){
@Override
protected TObjectHashingStrategy<K> getHashingStrategy() {
return hashingStrategy;
}
};
}
public StripedLockConcurrentHashMap(int initialCapacity) {
super(getInitCap(initialCapacity, DEFAULT_LOAD_FACTOR));
}
private static int getInitCap(int initialCapacity, float loadFactor) {
if (loadFactor <= 0 || initialCapacity < 0) {
throw new IllegalArgumentException();
}
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
int cap = 1;
while (cap < initialCapacity) {
cap <<= 1;
}
return cap;
}
/**
* Creates a new, empty map with a default initial capacity,
* load factor, and concurrencyLevel.
*/
public StripedLockConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY);
}
/**
* Creates a new map with the same mappings as the given map. The
* map is created with a capacity of twice the number of mappings in
* the given map or 11 (whichever is greater), and a default load factor
* and concurrencyLevel.
*
* @param t the map
*/
public StripedLockConcurrentHashMap(@NotNull Map<? extends K, ? extends V> t) {
this(Math.max((int)(t.size() / DEFAULT_LOAD_FACTOR) + 1, 11));
putAll(t);
}
// inherit Map javadoc
@Override
public boolean isEmpty() {
return count == 0;
}
// inherit Map javadoc
@Override
public int size() {
return count;
}
/**
* Returns the value to which the specified key is mapped in this table.
*
* @param key a key in the table.
* @return the value to which the key is mapped in this table;
* <tt>null</tt> if the key is not mapped to any value in
* this table.
* @throws NullPointerException if the key is
* <tt>null</tt>.
*/
@Override
public V get(@NotNull Object key) {
K kKey = (K)key;
int hash = getHashingStrategy().computeHashCode(kKey); // throws NullPointerException if key null
return get(kKey, hash);
}
/**
* Tests if the specified object is a key in this table.
*
* @param key possible key.
* @return <tt>true</tt> if and only if the specified object
* is a key in this table, as determined by the
* <tt>equals</tt> method; <tt>false</tt> otherwise.
* @throws NullPointerException if the key is
* <tt>null</tt>.
*/
@Override
public boolean containsKey(@NotNull Object key) {
K kKey = (K)key;
int hash = getHashingStrategy().computeHashCode(kKey); // throws NullPointerException if key null
return containsKey(kKey, hash);
}
/**
* Legacy method testing if some key maps into the specified value
* in this table. This method is identical in functionality to
* {@link #containsValue}, and exists solely to ensure
* full compatibility with class {@link java.util.Hashtable},
* which supported this method prior to introduction of the
* Java Collections framework.
*
* @param value a value to search for.
* @return <tt>true</tt> if and only if some key maps to the
* <tt>value</tt> argument in this table as
* determined by the <tt>equals</tt> method;
* <tt>false</tt> otherwise.
* @throws NullPointerException if the value is <tt>null</tt>.
*/
public boolean contains(@NotNull Object value) {
return containsValue(value);
}
/**
* Maps the specified <tt>key</tt> to the specified
* <tt>value</tt> in this table. Neither the key nor the
* value can be <tt>null</tt>.
* <p/>
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
*
* @param key the table key.
* @param value the value.
* @return the previous value of the specified key in this table,
* or <tt>null</tt> if it did not have one.
* @throws NullPointerException if the key or value is
* <tt>null</tt>.
*/
@Override
public V put(@NotNull K key, @NotNull V value) {
int hash = getHashingStrategy().computeHashCode(key);
return put(key, hash, value, false);
}
/**
* If the specified key is not already associated
* with a value, associate it with the given value.
* This is equivalent to
* <pre>
* if (!map.containsKey(key))
* return map.put(key, value);
* else
* return map.get(key);
* </pre>
* Except that the action is performed atomically.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key.
* @throws NullPointerException if the specified key or value is
* <tt>null</tt>.
*/
@Override
public V putIfAbsent(@NotNull K key, @NotNull V value) {
int hash = getHashingStrategy().computeHashCode(key);
return put(key, hash, value, true);
}
/**
* Copies all of the mappings from the specified map to this one.
* <p/>
* These mappings replace any mappings that this map had for any of the
* keys currently in the specified Map.
*
* @param t Mappings to be stored in this map.
*/
@Override
public void putAll(@NotNull Map<? extends K, ? extends V> t) {
for (Entry<? extends K, ? extends V> e : t.entrySet()) {
V value = e.getValue();
if (value != null) { // null is possible if the entry has just been removed
put(e.getKey(), value);
}
}
}
/**
* Removes the key (and its corresponding value) from this
* table. This method does nothing if the key is not in the table.
*
* @param key the key that needs to be removed.
* @return the value to which the key had been mapped in this table,
* or <tt>null</tt> if the key did not have a mapping.
* @throws NullPointerException if the key is
* <tt>null</tt>.
*/
@Override
public V remove(@NotNull Object key) {
K kKey = (K)key;
int hash = getHashingStrategy().computeHashCode(kKey);
return remove(kKey, hash, null);
}
/**
* Remove entry for key only if currently mapped to given value.
* Acts as
* <pre>
* if (map.get(key).equals(value)) {
* map.remove(key);
* return true;
* } else return false;
* </pre>
* except that the action is performed atomically.
*
* @param key key with which the specified value is associated.
* @param value value associated with the specified key.
* @return true if the value was removed
* @throws NullPointerException if the specified key is
* <tt>null</tt>.
*/
@Override
public boolean remove(@NotNull Object key, @NotNull Object value) {
K kKey = (K)key;
int hash = getHashingStrategy().computeHashCode(kKey);
return remove(kKey, hash, value) != null;
}
/**
* Replace entry for key only if currently mapped to given value.
* Acts as
* <pre>
* if (map.get(key).equals(oldValue)) {
* map.put(key, newValue);
* return true;
* } else return false;
* </pre>
* except that the action is performed atomically.
*
* @param key key with which the specified value is associated.
* @param oldValue value expected to be associated with the specified key.
* @param newValue value to be associated with the specified key.
* @return true if the value was replaced
* @throws NullPointerException if the specified key or values are
* <tt>null</tt>.
*/
@Override
public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) {
int hash = getHashingStrategy().computeHashCode(key);
return replace(key, hash, oldValue, newValue);
}
/**
* Replace entry for key only if currently mapped to some value.
* Acts as
* <pre>
* if ((map.containsKey(key)) {
* return map.put(key, value);
* } else return null;
* </pre>
* except that the action is performed atomically.
*
* @param key key with which the specified value is associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key.
* @throws NullPointerException if the specified key or value is
* <tt>null</tt>.
*/
@Override
public V replace(@NotNull K key, @NotNull V value) {
int hash = getHashingStrategy().computeHashCode(key);
return replace(key, hash, value);
}
/**
* Returns a set view of the keys contained in this map. The set is
* backed by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
* The view's returned <tt>iterator</tt> 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.
*
* @return a set view of the keys contained in this map.
*/
@NotNull
@Override
public Set<K> keySet() {
return new KeySet(); //conserve memory by not caching keyset
}
/**
* Returns a collection view of the values contained in this map. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
* The view's returned <tt>iterator</tt> 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.
*
* @return a collection view of the values contained in this map.
*/
@NotNull
@Override
public Collection<V> values() {
return new Values(); //conserve memory by not caching
}
/**
* Returns a collection view of the mappings contained in this map. Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are reflected in
* the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
* It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
* The view's returned <tt>iterator</tt> 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.
*
* @return a collection view of the mappings contained in this map.
*/
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
return new EntrySet(); //conserve memory by not caching
}
/**
* Returns an enumeration of the keys in this table.
*
* @return an enumeration of the keys in this table.
* @see #keySet
*/
public Enumeration<K> keys() {
return new KeyIterator();
}
/**
* Returns an enumeration of the values in this table.
*
* @return an enumeration of the values in this table.
* @see #values
*/
public Enumeration<V> elements() {
return new ValueIterator();
}
/* ---------------- Iterator Support -------------- */
abstract class HashIterator {
int nextSegmentIndex;
int nextTableIndex;
HashEntry[] currentTable;
HashEntry<K, V> nextEntry;
HashEntry<K, V> lastReturned;
HashIterator() {
nextSegmentIndex = 0;
nextTableIndex = -1;
advance();
}
public boolean hasMoreElements() {
return hasNext();
}
final void advance() {
if (nextEntry != null && (nextEntry = nextEntry.next) != null) {
return;
}
while (nextTableIndex >= 0) {
if ((nextEntry = (HashEntry<K, V>)currentTable[nextTableIndex--]) != null) {
return;
}
}
while (nextSegmentIndex >= 0) {
_CHMSegment seg = StripedLockConcurrentHashMap.this;
nextSegmentIndex--;
if (seg.count != 0) {
currentTable = seg.table;
for (int j = currentTable.length - 1; j >= 0; --j) {
if ((nextEntry = (HashEntry<K, V>)currentTable[j]) != null) {
nextTableIndex = j - 1;
return;
}
}
}
}
}
public boolean hasNext() {
return nextEntry != null;
}
HashEntry<K, V> nextEntry() {
if (nextEntry == null) {
throw new NoSuchElementException();
}
lastReturned = nextEntry;
advance();
return lastReturned;
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
StripedLockConcurrentHashMap.this.remove(lastReturned.key);
lastReturned = null;
}
}
final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> {
@Override
@NotNull
public K next() {
return nextEntry().key;
}
@Override
@NotNull
public K nextElement() {
return nextEntry().key;
}
}
final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> {
@Override
@NotNull
public V next() {
return nextEntry().value;
}
@Override
@NotNull
public V nextElement() {
return nextEntry().value;
}
}
/**
* Entry iterator. Exported Entry objects must write-through
* changes in setValue, even if the nodes have been cloned. So we
* cannot return internal HashEntry objects. Instead, the iterator
* itself acts as a forwarding pseudo-entry.
*/
final class EntryIterator extends HashIterator implements Entry<K, V>, Iterator<Entry<K, V>> {
@Override
@NotNull
public Entry<K, V> next() {
nextEntry();
return this;
}
@Override
@NotNull
public K getKey() {
if (lastReturned == null) {
throw new IllegalStateException("Entry was removed");
}
return lastReturned.key;
}
@Override
@Nullable("null means the entry has just been removed")
public V getValue() {
if (lastReturned == null) {
throw new IllegalStateException("Entry was removed");
}
return get(lastReturned.key);
}
@Override
public V setValue(@NotNull V value) {
if (lastReturned == null) {
throw new IllegalStateException("Entry was removed");
}
return put(lastReturned.key, value);
}
public boolean equals(Object o) {
// If not acting as entry, just use default.
if (lastReturned == null) {
return super.equals(o);
}
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry)o;
K o1 = getKey();
K o2 = (K)e.getKey();
return getHashingStrategy().equals(o1, o2) && getValue().equals(e.getValue());
}
public int hashCode() {
// If not acting as entry, just use default.
if (lastReturned == null) {
return super.hashCode();
}
Object k = getKey();
Object v = getValue();
return k.hashCode() ^ v.hashCode();
}
public String toString() {
// If not acting as entry, just use default.
if (lastReturned == null) {
return super.toString();
}
return getKey() + "=" + getValue();
}
}
final class KeySet extends AbstractSet<K> {
@NotNull
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return StripedLockConcurrentHashMap.this.size();
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
@Override
public boolean remove(Object o) {
return StripedLockConcurrentHashMap.this.remove(o) != null;
}
@Override
public void clear() {
StripedLockConcurrentHashMap.this.clear();
}
@NotNull
@Override
public Object[] toArray() {
Collection<K> c = new ArrayList<K>();
for (K k : this) {
c.add(k);
}
return c.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
Collection<K> c = new ArrayList<K>();
for (K k : this) {
c.add(k);
}
return c.toArray(a);
}
}
final class Values extends AbstractCollection<V> {
@NotNull
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return StripedLockConcurrentHashMap.this.size();
}
@Override
public boolean contains(Object o) {
return containsValue(o);
}
@Override
public void clear() {
StripedLockConcurrentHashMap.this.clear();
}
@NotNull
@Override
public Object[] toArray() {
Collection<V> c = new ArrayList<V>();
for (V k : this) {
c.add(k);
}
return c.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
Collection<V> c = new ArrayList<V>();
for (V k : this) {
c.add(k);
}
return c.toArray(a);
}
}
final class EntrySet extends AbstractSet<Entry<K, V>> {
@NotNull
@Override
public Iterator<Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<K, V> e = (Entry<K, V>)o;
V v = get(e.getKey());
return v != null && v.equals(e.getValue());
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry<K, V> e = (Entry<K, V>)o;
return StripedLockConcurrentHashMap.this.remove(e.getKey(), e.getValue());
}
@Override
public int size() {
return StripedLockConcurrentHashMap.this.size();
}
@Override
public void clear() {
StripedLockConcurrentHashMap.this.clear();
}
@NotNull
@Override
public Object[] toArray() {
// Since we don't ordinarily have distinct Entry objects, we
// must pack elements using exportable SimpleEntry
Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size());
for (Entry<K, V> i : this) {
c.add(new SimpleEntry(i));
}
return c.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size());
for (Entry<K, V> i : this) {
c.add(new SimpleEntry(i));
}
return c.toArray(a);
}
}
/**
* This duplicates java.util.AbstractMap.SimpleEntry until this class
* is made accessible.
*/
final class SimpleEntry implements Entry<K, V> {
final K key;
V value;
public SimpleEntry(@NotNull Entry<K, V> e) {
key = e.getKey();
value = e.getValue();
}
@Override
@NotNull
public K getKey() {
return key;
}
@Override
@NotNull
public V getValue() {
return value;
}
@Override
public V setValue(@NotNull V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry)o;
K o2 = (K)e.getKey();
return getHashingStrategy().equals(key, o2) && value.equals(e.getValue());
}
public int hashCode() {
return key.hashCode() ^ value.hashCode();
}
public String toString() {
return key + "=" + value;
}
}
static class CanonicalHashingStrategy<K> implements TObjectHashingStrategy<K> {
private static final CanonicalHashingStrategy INSTANCE = new CanonicalHashingStrategy();
@SuppressWarnings("unchecked")
static <K> CanonicalHashingStrategy<K> getInstance() {
return INSTANCE;
}
@Override
public int computeHashCode(final K object) {
int h = object.hashCode();
// performance matters here
//h += ~(h << 9);
//h ^= h >>> 14;
//h += h << 4;
//h ^= h >>> 10;
return h;
}
@Override
public boolean equals(@NotNull K o1, @NotNull K o2) {
return o1.equals(o2);
}
}
}
/**
* Segments are specialized versions of hash tables. This
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
*/
class _CHMSegment<K, V> {
private static final StripedReentrantLocks STRIPED_REENTRANT_LOCKS = StripedReentrantLocks.getInstance();
private final byte lockIndex = (byte)STRIPED_REENTRANT_LOCKS.allocateLockIndex();
private void lock() {
STRIPED_REENTRANT_LOCKS.lock((int)lockIndex & 0xff);
if (modificationBlocked) throw new ConcurrentModificationException();
}
private void unlock() {
STRIPED_REENTRANT_LOCKS.unlock((int)lockIndex & 0xff);
}
/*
* 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;
/*
* The table is rehashed when its size exceeds this threshold.
*/
private int threshold() {
return (int)(table.length * loadFactor);
}
/**
* The per-segment table. Declared as a raw type, casted
* to HashEntry<K,V> on each use.
*/
volatile HashEntry[] table;
private static final float loadFactor = 0.75f;
private volatile boolean modificationBlocked; // the only state transition is 0 -> 1
_CHMSegment(int initialCapacity) {
setTable(new HashEntry[initialCapacity]);
}
public void blockModification() {
try {
lock();
modificationBlocked = true;
}
finally {
unlock();
}
}
/*
* Set table to new HashEntry array.
* Call only while holding lock or in constructor.
*/
void setTable(HashEntry[] newTable) {
table = newTable;
}
/*
* Return properly casted first entry of bin for given hash
*/
HashEntry<K, V> getFirst(int hash) {
HashEntry[] tab = table;
return tab[hash & tab.length - 1];
}
/*
* Read value field of an entry under lock. Called if value
* field ever appears to be null. This is possible only if a
* compiler happens to reorder a HashEntry initialization with
* its table assignment, which is legal under memory model
* but is not known to ever occur.
*/
V readValueUnderLock(HashEntry<K, V> e) {
try {
lock();
return e.value;
}
finally {
unlock();
}
}
/* Specialized implementations of map methods */
V get(K key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && getHashingStrategy().equals(key, e.key)) {
V v = e.value;
if (v != null) {
return v;
}
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
boolean containsKey(K key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && getHashingStrategy().equals(key, e.key)) {
return true;
}
e = e.next;
}
}
return false;
}
public boolean containsValue(Object value) {
if (count != 0) { // read-volatile
HashEntry[] tab = table;
int len = tab.length;
for (int i = 0; i < len; i++) {
for (HashEntry<K, V> e = tab[i]; e != null; e = e.next) {
V v = e.value;
if (v == null) // recheck
{
v = readValueUnderLock(e);
}
if (value.equals(v)) {
return true;
}
}
}
}
return false;
}
boolean replace(@NotNull K key, int hash, @NotNull V oldValue, @NotNull V newValue) {
try {
lock();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !getHashingStrategy().equals(key, e.key))) {
e = e.next;
}
boolean replaced = false;
if (e != null && oldValue.equals(e.value)) {
replaced = true;
e.value = newValue;
}
return replaced;
}
finally {
unlock();
}
}
V replace(@NotNull K key, int hash, @NotNull V newValue) {
try {
lock();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !getHashingStrategy().equals(key, e.key))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
oldValue = e.value;
e.value = newValue;
}
return oldValue;
}
finally {
unlock();
}
}
V put(@NotNull K key, int hash, @NotNull V value, boolean onlyIfAbsent) {
try {
lock();
int c = count;
if (c++ > threshold()) // ensure capacity
{
rehash();
}
HashEntry[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
while (e != null && (e.hash != hash || !getHashingStrategy().equals(key, e.key))) {
e = e.next;
}
V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
}
}
else {
oldValue = null;
tab[index] = new HashEntry<K, V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
}
finally {
unlock();
}
}
void rehash() {
HashEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= StripedLockConcurrentHashMap.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.
*/
HashEntry[] newTable = new HashEntry[oldCapacity << 1];
int sizeMask = newTable.length - 1;
for (int i = 0; i < oldCapacity; i++) {
// We need to guarantee that any existing reads of old Map can
// proceed. So we cannot yet null out each bin.
HashEntry<K, V> e = oldTable[i];
if (e != null) {
HashEntry<K, V> next = e.next;
int idx = e.hash & sizeMask;
// Single node on list
if (next == null) {
newTable[idx] = e;
}
else {
// Reuse trailing consecutive sequence at same slot
HashEntry<K, V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K, V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone all remaining nodes
for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {
int k = p.hash & sizeMask;
HashEntry<K, V> n = newTable[k];
newTable[k] = new HashEntry<K, V>(p.key, p.hash, n, p.value);
}
}
}
}
setTable(newTable);
}
/*
* Remove; match on key only if value null, else match both.
*/
V remove(@NotNull K key, int hash, @Nullable("null means don't care") Object value) {
try {
lock();
int c = count - 1;
HashEntry[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
while (e != null && (e.hash != hash || !getHashingStrategy().equals(key, e.key))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
V v = e.value;
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
HashEntry<K, V> newFirst = e.next;
for (HashEntry<K, V> p = first; p != e; p = p.next) {
newFirst = new HashEntry<K, V>(p.key, p.hash, newFirst, p.value);
}
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
}
finally {
unlock();
}
}
public void clear() {
if (count != 0) {
try {
lock();
HashEntry[] tab = table;
for (int i = 0; i < tab.length; i++) {
tab[i] = null;
}
count = 0; // write-volatile
}
finally {
unlock();
}
}
}
protected TObjectHashingStrategy<K> getHashingStrategy() {
return StripedLockConcurrentHashMap.CanonicalHashingStrategy.getInstance();
}
/**
* ConcurrentHashMap list entry. Note that this is never exported
* out as a user-visible Map.Entry.
* <p/>
* Because the value field is volatile, not final, it is legal wrt
* the Java Memory Model for an unsynchronized reader to see null
* instead of initial value when read via a data race. Although a
* reordering leading to this is not likely to ever actually
* occur, the Segment.readValueUnderLock method is used as a
* backup in case a null (pre-initialized) value is ever seen in
* an unsynchronized access method.
*/
static final class HashEntry<K, V> {
@NotNull final K key;
final int hash;
@NotNull volatile V value;
final HashEntry<K, V> next;
HashEntry(@NotNull K key, int hash, HashEntry<K, V> next, @NotNull V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
}
}
}