All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.util.SegmentedHashMap Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.util;


import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.collections.AbstractStableIterator;

import java.lang.ref.WeakReference;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;


/**
* An implementation of java.util.Map that is optimized for heavy concurrent use.
* 

* Retrieval and update operations to the map (e.g. get, put) * are non-blocking and uncontended and will reflect some state of the map. * Insert and remove operations to the map (e.g. put, remove) * do require internal locking. *

* The entries in the map are internally segmented so as to permit a high level * of concurrent "locked" operations without contention. *

* Retrievals and updates that run concurrently with bulk operations * (e.g. putAll, clear may reflect insertion or removal of * only some entries. Iterators on the Map may also reflect concurrent updates * made since the Iterator was created. However, Iterators will not throw * ConcurrentModificationException. * * @since Coherence 3.5 * @author rhl 2008.12.01 */ public class SegmentedHashMap extends Base implements Map { // ----- constructors --------------------------------------------------- /** * Default constructor. */ public SegmentedHashMap() { this(DEFAULT_INITIALSIZE, DEFAULT_LOADFACTOR, DEFAULT_GROWTHRATE); } /** * Construct a thread-safe hash map using the specified settings. * * @param cInitialBuckets the initial number of hash buckets, 0 < n * @param flLoadFactor the acceptable load factor before resizing * occurs, 0 < n, such that a load factor of * 1.0 causes resizing when the number of entries * exceeds the number of buckets * @param flGrowthRate the rate of bucket growth when a resize occurs, * 0 < n, such that a growth rate of 1.0 will * double the number of buckets: * bucketcount = bucketcount * (1 + growthrate) */ public SegmentedHashMap(int cInitialBuckets, float flLoadFactor, float flGrowthRate) { if (cInitialBuckets <= 0) { throw new IllegalArgumentException("SegmentedHashMap: " + "Initial number of buckets must be greater than zero."); } if (flLoadFactor <= 0) { throw new IllegalArgumentException("SegmentedHashMap: " + "Load factor must be greater than zero."); } if (flGrowthRate <= 0) { throw new IllegalArgumentException("SegmentedHashMap: " + "Growth rate must be greater than zero."); } // initialize the hash map data structure m_aeBucket = new Entry[cInitialBuckets]; m_cSegmentCapacity = Math.max((int) (cInitialBuckets * flLoadFactor) / SEGMENT_COUNT, MIN_SEGMENT_CAPACITY); m_flLoadFactor = flLoadFactor; m_flGrowthRate = flGrowthRate; // Initialize the segment control structures m_aSegment = new Segment[LOCK_COUNT]; for (int i = 0; i < LOCK_COUNT; i++) { m_aSegment[i] = new Segment(); } initializeActions(); } // ----- accessors ------------------------------------------------------ /** * Return the registered action for insert. * * @return the registered action for insert */ protected InsertAction getInsertAction() { return m_actionInsert; } /** * Specify the action for insert. * * @param action the action for insert */ protected void setInsertAction(InsertAction action) { m_actionInsert = action; } /** * Return the registered action for getEntryInternal. * * @return the registered action for getEntryInternal */ protected GetEntryAction getGetEntryAction() { return m_actionGetEntry; } /** * Specify the action for getEntryInternal. * * @param action the action for getEntryInternal */ protected void setGetEntryAction(GetEntryAction action) { m_actionGetEntry = action; } /** * Return the registered action for remove(). * * @return the registered action for remove() */ protected RemoveAction getRemoveAction() { return m_actionRemove; } /** * Specify the action for remove(). * * @param action the action for remove() */ protected void setRemoveAction(RemoveAction action) { m_actionRemove = action; } /** * Return the registered action for containsValue(). * * @return the registered action for containsValue() */ protected ContainsValueAction getContainsValueAction() { return m_actionContainsValue; } /** * Specify the action for containsValue(). * * @param action the action for containsValue() */ protected void setContainsValueAction(ContainsValueAction action) { m_actionContainsValue = action; } // ----- Object methods ------------------------------------------------- /** * Compares the specified object with this map for equality. Returns * true if the given object is also a map and the two maps * represent the same mappings. More formally, two maps t1 and * t2 represent the same mappings if * t1.keySet().equals(t2.keySet()) and for every key k * in t1.keySet(), (t1.get(k)==null ? t2.get(k)==null : * t1.get(k).equals(t2.get(k))) . This ensures that the * equals method works properly across different implementations * of the map interface. * * @param oThat object to be compared for equality with this Map * * @return true if the specified object is equal to this Map */ public boolean equals(Object oThat) { if (oThat == this) { return true; } if (!(oThat instanceof Map)) { return false; } Map mapThat = (Map) oThat; if (mapThat.size() != this.size()) { return false; } for (Iterator iter = mapThat.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entryThat = (Map.Entry) iter.next(); Entry entryThis = getEntryInternal(entryThat.getKey()); if (!Base.equals(entryThis, entryThat)) { return false; } } return true; } /** * Returns the hash code value for this Map. The hash code of a Map is * defined to be the sum of the hash codes of each entry in the Map's * entrySet() view. This ensures that t1.equals(t2) * implies that t1.hashCode()==t2.hashCode() for any two maps * t1 and t2, as required by the general contract of * Object.hashCode. * * @return the hash code value for this Map */ public int hashCode() { int nHash = 0; for (Iterator iter = entrySet().iterator(); iter.hasNext(); ) { nHash += iter.next().hashCode(); } return nHash; } /** * Returns a String representation of this map. * * @return a String representation of this map */ public String toString() { StringBuffer buf = new StringBuffer(); buf.append("{"); Iterator iter = entrySet().iterator(); boolean fHasNext = iter.hasNext(); while (fHasNext) { Entry entry = (Entry) iter.next(); Object oKey = entry.getKey(); Object oValue = entry.getValue(); buf.append(oKey == this ? "(this Map)" : oKey); buf.append("="); buf.append(oValue == this ? "(this Map)" : oValue); if (fHasNext = iter.hasNext()) { buf.append(", "); } } buf.append("}"); return buf.toString(); } // ----- Map interface -------------------------------------------------- /** * Returns the number of key-value mappings in this map. *

* Note: Unlike some Map implementations, the size() operation on * this map may be relatively expensive. * * @return the number of key-value mappings in this map */ public int size() { // On a JSR-133 compliant JVM, doing a read from the atomic counter // will guarantee up-to-date values for the cEntries field on all // segments. On non-compliant JVMs, we accept that the value returned // may be stale. m_atomicLocks.get(); // If there are no synthetic entries, just sum the Entry counters. // Subclasses that may introduce synthetic entries must override // size() to provide a suitable implementation to account for // synthetics. int cEntries = 0; Segment[] aSegment = m_aSegment; for (int i = 0; i < SEGMENT_COUNT; i++) { cEntries += aSegment[i].cEntries; } return cEntries; } /** * Returns true if this map contains no key-value mappings. * * @return true if this map contains no key-value mappings */ public boolean isEmpty() { return size() == 0; } /** * Returns true iff this map contains a mapping for the specified * key. * * @param oKey key whose presence in this map is to be tested * * @return true iff this map contains a mapping for the specified * key */ public boolean containsKey(Object oKey) { return getEntryInternal(oKey) != null; } /** * Returns true if this map maps one or more keys to the * specified value. * * @param oValue value whose presence in this map is to be tested * * @return true if this map maps one or more keys to the * specified value */ public boolean containsValue(Object oValue) { ContainsValueAction actionEntry = getContainsValueAction(); // if there was an intervening resize and the value was not found, // apply the action again as the Entry may have been missed. Entry[] aeBucket; do { Object oContext = actionEntry.instantiateContext(oValue); aeBucket = getStableBucketArray(); invokeOnAllKeys(oContext, /*fLock*/ false, actionEntry); if (actionEntry.isFound(oContext)) { return true; } } while (aeBucket != getStableBucketArray()); return false; } /** * Returns the value to which this map maps the specified key. Returns * null if the map contains no mapping for this key. A return * value of null does not necessarily indicate that the map * contains no mapping for the key; it's also possible that the map * explicitly maps the key to null. The containsKey * operation may be used to distinguish these two cases. * * @param oKey key whose associated value is to be returned * * @return the value to which this map maps the specified key, or * null if the map contains no mapping for this key */ public Object get(Object oKey) { Entry entry = getEntryInternal(oKey); return (entry == null ? null : entry.getValue()); } /** * Locate an Entry in the this map based on its key. * * @param key the key object to search for * * @return the Entry or null if the entry does not exist */ public Map.Entry getEntry(Object key) { return getEntryInternal(key); } /** * Associates the specified value with the specified key in this map. If * the map previously contained a mapping for this key, the old value is * replaced. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key */ public Object put(Object oKey, Object oValue) { Object oOrig = putInternal(oKey, oValue); return oOrig == NO_VALUE ? null : oOrig; } /** * Copies all of the mappings from the specified map to this map. * putAll is semantically equivalent to: *

    * for (Iterator iter = mapOther.entrySet().iterator(); iter.hasNext(); )
    *     {
    *     Map.Entry entry = (Map.Entry) iter.next();
    *     put(entry.getKey(), entry.getValue());
    *     }
    * 
* * @param mapOther mappings to be stored in this map */ public void putAll(Map mapOther) { // If the insert is fairly small, take the naive approach of iterating // over the supplied Map's entrySet and inserting each entry. // // For a large insert, we would expect to have to lock each segment // one or more times with the naive approach. Instead lock all of the // segments at once and do the updates/inserts while holding all of // the segment-locks. int nSizeOther = mapOther.size(); if (nSizeOther < PUTALL_THRESHOLD) { // take the naive approach for (Iterator iter = mapOther.entrySet().iterator(); iter.hasNext();) { Map.Entry entryOther = (Map.Entry) iter.next(); putInternal(entryOther.getKey(), entryOther.getValue()); } } else { // Define an action to update or insert. // The action will run while all segments are locked. EntryAction actionPut = new InsertAction() { public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { Object oResult = super.invokeFound(oKey, oContext, aeBucket, nBucket, entryPrev, entryCur); if (oResult == NO_VALUE) { // update entryCur.setValueInternal(oContext); } return oResult; } }; // lock all of the buckets, and perform the updates/inserts while // holding all of the segment locks. For a big putAll(), this is // faster than locking each key individually. lockAllBuckets(); try { if (nSizeOther > m_cSegmentCapacity * SEGMENT_COUNT) { // grow to at least the size of the user-supplied map grow(nSizeOther); } for (Iterator iter = mapOther.entrySet().iterator(); iter.hasNext();) { Map.Entry entryOther = (Map.Entry) iter.next(); // invoke the put action without additional locking // because we have already locked all buckets invokeOnKey(entryOther.getKey(), entryOther.getValue(), /*fLock*/ false, actionPut); } } finally { unlockAllBuckets(); } } } /** * Removes the mapping for this key from this map if present. * * @param oKey key whose mapping is to be removed from the map * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key */ public Object remove(Object oKey) { Object oOrig = removeInternal(oKey, getRemoveAction(), /*oContext*/null); return oOrig == NO_VALUE ? null : oOrig; } /** * Removes all mappings from this map. */ public void clear() { // If there are no synthetic entries, just clear and reset the bucket // array. Subclasses that may introduce synthetic entries must // override clear() to provide a suitable implementation to account // for synthetics. lockAllBuckets(); try { m_aeBucket = new Entry[DEFAULT_INITIALSIZE]; // clear the per-segment entry counters Segment[] aSegment = m_aSegment; for (int i = 0; i < SEGMENT_COUNT; i++) { aSegment[i].cEntries = 0; } m_cSegmentCapacity = Math.max((int) (DEFAULT_INITIALSIZE * m_flLoadFactor) / SEGMENT_COUNT, MIN_SEGMENT_CAPACITY); } finally { unlockAllBuckets(); } } /** * Returns a set view of the mappings contained in this map. Each element * in the returned set is a Map.Entry. The set is backed by the * map, so changes to the map are reflected in the set, and vice-versa. * If the map is modified while an iteration over the set is in progress, * the results of the iteration are undefined. The set supports element * removal, which removes the corresponding mapping from the map, via the * Iterator.remove, Set.remove, removeAll, * retainAll and clear operations. It does not support * the add or addAll operations. * * @return a set view of the mappings contained in this map. */ public Set entrySet() { // no need to synchronize; it is acceptable that two threads would // instantiate an entry set EntrySet set = m_setEntries; if (set == null) { m_setEntries = set = instantiateEntrySet(); } return set; } /** * 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. (If the map is modified while an iteration over * the Set is in progress, the results of the iteration are undefined.) * The Set supports element removal, which removes the corresponding entry * from the map, via the Iterator.remove, Set.remove, removeAll * retainAll, and clear operations. It does not support the add or * addAll operations. * * @return a Set view of the keys contained in this map */ public Set keySet() { // no need to synchronize; it is acceptable that two threads would // instantiate a key set KeySet set = m_setKeys; if (set == null) { m_setKeys = set = instantiateKeySet(); } return set; } /** * 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. If the map is modified while an * iteration over the collection is in progress, the results of the * iteration are undefined. The collection supports element removal, * which removes the corresponding mapping from the map, via the * Iterator.remove, Collection.remove, * removeAll, retainAll and clear operations. * It does not support the add or addAll operations. * * @return a collection view of the values contained in this map */ public Collection values() { // no need to synchronize; it is acceptable that two threads would // instantiate a values collection ValuesCollection col = m_colValues; if (col == null) { m_colValues = col = instantiateValuesCollection(); } return col; } // ----- helpers -------------------------------------------------------- /** * Initialize the EntryAction's for this map. */ protected void initializeActions() { /* getEntryInternal action */ setGetEntryAction(instantiateGetEntryAction()); /* insert action */ setInsertAction(instantiateInsertAction()); /* remove action */ setRemoveAction(instantiateRemoveAction()); /* containsValue action */ setContainsValueAction(instantiateContainsValueAction()); } /** * Locate an Entry in the hash map based on its key. * * @param oKey the key object to search for * * @return the Entry or null */ protected Entry getEntryInternal(Object oKey) { return getEntryInternal(oKey, /*fSynthetic*/ false); } /** * Locate an Entry in the hash map based on its key. * * @param oKey the key object to search for * @param fSynthetic include synthetic Entry objects representing keys * that are not contained in the map * * @return the Entry or null */ protected Entry getEntryInternal(Object oKey, boolean fSynthetic) { Object oResult; Boolean FSynthetic = fSynthetic ? Boolean.TRUE : Boolean.FALSE; do { oResult = invokeOnKey(oKey, FSynthetic, /*fLock*/ false, getGetEntryAction()); } while (oResult == NO_VALUE); return (Entry) oResult; } /** * Associates the specified value with the specified key in this map. If * the map previously contained a mapping for this key, the old value is * replaced. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * * @return previous value associated with specified key, or * NO_VALUE if there was no mapping for key. A * null return indicates that the map previously * associated null with the specified key */ protected Object putInternal(Object oKey, Object oValue) { return putInternal(oKey, oValue, /*fOnlyIfAbsent*/false); } /** * Associates the specified value with the specified key in this map. If * the map previously contained a mapping for this key, the old value is * replaced. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * @param fOnlyIfAbsent if true, perform a logical insert only; no action * if the key already exists * * @return previous value associated with specified key, or * NO_VALUE if there was no mapping for key. A * null return indicates that the map previously * associated null with the specified key */ protected Object putInternal(Object oKey, Object oValue, boolean fOnlyIfAbsent) { while (true) { Entry entry = getEntryInternal(oKey, /*fSynthetic*/ true); if (entry == null || entry.isSynthetic()) { // If an entry is not found, or the entry is synthetic, it is a // logical insert (and must be done under a segment lock). Object oResult = invokeOnKey(oKey, oValue, /*fLock*/ true, getInsertAction()); if (oResult == NO_VALUE) { // another thread inserted; try again continue; } if (entry == null) { // An Entry was added. Check to see if the map should be // grown in order to encourage balanced buckets. ensureLoadFactor(getSegmentForKey(oKey)); } // successfully inserted return NO_VALUE; } // In the case of an update, it is possible that the Entry is // being removed or the value is being updated by another thread return !fOnlyIfAbsent ? entry.setValueInternal(oValue) : NO_VALUE; } } /** * Removes the mapping for this key from this map if present. * * @param oKey key whose mapping is to be removed from the map * @param actionRemove the EntryAction to apply * @param oContext the context for the remove action * * @return previous value associated with specified key, or * NO_VALUE if there was no mapping for key. A * null return indicates that the map previously * associated null with the specified key. */ protected Object removeInternal(Object oKey, EntryAction actionRemove, Object oContext) { return invokeOnKey(oKey, oContext, /*fLock*/ true, actionRemove); } /** * Apply the specified toArray() action to the entries in the map. The * toArray() action is not applied under any segment lock and is expected * to accept a List instance as a context. * * @param action the toArray() action * @param a the array into which the elements of the Collection * are to be stored, if it is big enough; otherwise, a * new array of the same runtime type is allocated for * this purpose * * @return an array containing the elements returned by the specified * action * * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of the elements returned * by the specified action */ protected Object[] toArrayInternal(IterableEntryAction action, Object[] a) { List list = new ArrayList(); Entry[] aeBucket; // if there was an intervening resize, apply the action again as // Entry's may have been missed or repeated during the resize. do { list.clear(); aeBucket = getStableBucketArray(); invokeOnAllKeys(list, /*fLock*/ false, action); } while (aeBucket != getStableBucketArray()); return list.toArray(a == null ? new Object[list.size()] : a); } /** * Check whether or not the specified segment is overloaded and if so, * grow the bucket array (which suggests with high probability that the * per-segment load will decrease). * * @param segment the segment to ensure the load-factor for */ protected void ensureLoadFactor(Segment segment) { // Note: reading the segment size is a dirty read. The caller is // expected to have recently held the segment lock. if (segment.cEntries > m_cSegmentCapacity) { Entry[] aeBucket = m_aeBucket; lockAllBuckets(); try { // check that some other thread hasn't already grown if (m_aeBucket == aeBucket) { grow(); } } finally { unlockAllBuckets(); } } } /** * Resize the bucket array, rehashing all Entries. *

* Note: caller of this method is expected to hold locks on all segments of * the map while making this call. */ protected void grow() { Entry[] aeOld = m_aeBucket; int cOld = aeOld.length; // check if there is no more room to grow if (cOld >= BIGGEST_MODULO) { return; } // calculate growth int cNew = (int) Math.min((long) (cOld * (1F + m_flGrowthRate)), BIGGEST_MODULO); grow(cNew); } /** * Resize the bucket array to the specified size, rehashing all Entries. *

* Note: caller of this method is expected to hold locks on all segments of * the map while making this call. * * @param cNew the minimum size to attempt to grow to */ protected synchronized void grow(int cNew) { if (isActiveIterator()) { // don't grow if there are active iterators return; } synchronized (RESIZING) { // store off the old bucket array Entry[] aeOld = m_aeBucket; int cOld = aeOld.length; // use NO_ENTRIES to signify that a resize is taking place m_aeBucket = NO_ENTRIES; if (cNew <= cOld) { // very low growth rate or very low initial size. In either // case, ensure that at least some growth happens. cNew = cOld + 1; } // use a prime number at least as big than the new size int[] aiModulo = PRIME_MODULO; int cModulos = aiModulo.length; for (int i = 0; i < cModulos; ++i) { int iModulo = aiModulo[i]; if (iModulo >= cNew) { cNew = iModulo; break; } } // create a new bucket array; in the case of an OutOfMemoryError // be sure to restore the old bucket array Entry[] aeNew; try { aeNew = new Entry[cNew]; } catch (OutOfMemoryError e) { m_aeBucket = aeOld; throw e; } // rehash Segment[] aSegment = m_aSegment; for (int i = 0; i < cOld; ++i) { Entry entry = aeOld[i]; int nSegmentOld = getSegmentIndex(i); while (entry != null) { // store off the next Entry // (it is going to get hammered otherwise) Entry entryNext = entry.nextEntry(true); // rehash the Entry into the new bucket array Object oKey = entry.getKey(); int nHash = oKey == null ? 0 : oKey.hashCode(); int nBucket = getBucketIndex(nHash, cNew); int nSegmentNew = getSegmentIndex(nBucket); entry.setNext(aeNew[nBucket]); aeNew[nBucket] = entry; // update the counters --aSegment[nSegmentOld].cEntries; ++aSegment[nSegmentNew].cEntries; // process next Entry in the old list entry = entryNext; } } // store updated bucket array m_cSegmentCapacity = Math.max((int) (cNew * m_flLoadFactor) / SEGMENT_COUNT, MIN_SEGMENT_CAPACITY); m_aeBucket = aeNew; } } /** * Perform an action on all Entries in the map. The action is provided as * an EntryAction and (if the fLock is specified) it are * invoked while holding the locks for all segments. *

* The semantics of invokeOnAllKeys are equivalent to: *

    * for (Iterator iter = entrySet().iterator(); iter.hasNext(); )
    *     {
    *     Entry entry = (Entry) iter.next();
    *     actionEntry.invokeFound(...);
    *     }
    * return oContext;
    * 
* Except that if fLock is specified, it is performed atomically * while holding all segment-locks. * * @param oContext opaque context for the specified action * @param fLock true if all segment-locks should be * acquired before invoking the specified action * @param actionEntry the action to perform for each entry * * @return the specified opaque context */ protected Object invokeOnAllKeys(Object oContext, boolean fLock, IterableEntryAction actionEntry) { if (fLock) { lockAllBuckets(); } try { Entry[] aeBucket = getStableBucketArray(); int cBuckets = aeBucket.length; for (int iBucket = 0; iBucket < cBuckets; ++iBucket) { // walk all entries in the bucket Entry entry = aeBucket[iBucket]; Entry entryPrev = null; while (entry != null) { // invoke the action on each Entry Entry entryNext = entry.nextEntry(/*fSynthetic*/ true); actionEntry.invokeFound(entry.getKey(), oContext, aeBucket, iBucket, entryPrev, entry); if (actionEntry.isComplete(oContext)) { return oContext; } // recalculate entryPrev for the next Entry. Have to // consider that the action may have removed this Entry // from the bucket-list. entryPrev = ((entryPrev == null ? aeBucket[iBucket] != entry : entryPrev.nextEntry(true) != entry) ? entryPrev : entry); entry = entryNext; } } } finally { if (fLock) { unlockAllBuckets(); } } return oContext; } /** * Perform an action on the specified key. The action operation is * provided as an EntryAction and (if fLock is specified), is * invoked while holding the appropriate segment lock for the key. *

* The semantics of invokeOnKey are equivalent to: *

    * Object oResult;
    * if (containsKey(oKey))
    *     {
    *     oResult = action.invokeFound(...);
    *     }
    * else
    *     {
    *     oResult = action.invokeNotFound(...);
    *     }
    * return oResult;
    * 
* Except that if fLock is specified, it is performed atomically * while holding the segment-lock. * * @param oKey the key to act on * @param oContext opaque context for the specified action * @param fLock true iff the segment should be locked before invoking * the specified action * @param action the action to invoke * * @return the result of performing the action */ protected Object invokeOnKey(Object oKey, Object oContext, boolean fLock, EntryAction action) { int nHash = (oKey == null ? 0 : oKey.hashCode()); while (true) { Entry[] aeBucket = getStableBucketArray(); int cBuckets = aeBucket.length; int nBucket = getBucketIndex(nHash, cBuckets); if (fLock) { lockBucket(nBucket); } try { if (aeBucket != m_aeBucket) { continue; } // walk the linked list of entries (open hash) in the bucket Entry entryCur = aeBucket[nBucket]; Entry entryPrev = null; while (entryCur != null) { Entry entryNext = entryCur.nextEntry(true); // optimization: check hash first if (nHash == entryCur.m_nHash && Base.equals(oKey, entryCur.getKey())) { return action.invokeFound(oKey, oContext, aeBucket, nBucket, entryPrev, entryCur); } entryPrev = entryCur; entryCur = entryNext; } return action.invokeNotFound(oKey, oContext, aeBucket, nBucket); } finally { if (fLock) { unlockBucket(nBucket); } } } } /** * Calculate the bucket number for a particular hash code. * * @param nHash the hash code * @param cBuckets the number of buckets * * @return the bucket index for the specified hash code */ protected int getBucketIndex(int nHash, int cBuckets) { return (int) ((((long) nHash) & 0xFFFFFFFFL) % ((long) cBuckets)); } /** * Calculate the segment index for the the specified bucket. * * @param nBucket the bucket number * * @return the segment index */ protected int getSegmentIndex(int nBucket) { return nBucket % SEGMENT_COUNT; } /** * Return the Segment object for the specified key. * * @param oKey the key * * @return the Segment for the specified key */ protected Segment getSegmentForKey(Object oKey) { Entry[] aeBucket = getStableBucketArray(); int cBuckets = aeBucket.length; int nHash = (oKey == null ? 0 : oKey.hashCode()); int nBucket = getBucketIndex(nHash, cBuckets); return m_aSegment[getSegmentIndex(nBucket)]; } /** * Get the bucket array, or if a resize is occurring, wait for the resize * to complete and return the new bucket array. * * @return the latest bucket array */ protected Entry[] getStableBucketArray() { // get the bucket array Entry[] aeBucket = m_aeBucket; if (aeBucket == NO_ENTRIES) { // wait for the ongoing resize to complete synchronized (RESIZING) { return m_aeBucket; } } return aeBucket; } /** * Register the activation of an Iterator. *

* Note: The map will not grow while there are any active iterators. * * @param iter the activated iterator */ protected synchronized void iteratorActivated(Iterator iter) { Object oIterActive = m_oIterActive; if (oIterActive == null) { // optimize for single active iterator m_oIterActive = new WeakReference(iter); } else if (oIterActive instanceof WeakReference) { Object oPrev = ((WeakReference) oIterActive).get(); if (oPrev == null) { // previous Iterator was GC'd, replace it m_oIterActive = new WeakReference(iter); } else { // switch from single to multiple active iterators; // WeakHashMap will automatically clean up any GC'd iterators Map map = new WeakHashMap(); m_oIterActive = map; map.put(oPrev, null); map.put(iter, null); } } else { // it's a map, add additional active iterator ((Map) oIterActive).put(iter, null); } } /** * Release the (formerly-active) Iterator. *

* Note: This method could be used to accelerate the destruction of * unexhausted iterators. * * @param iter the iterator to be released */ public synchronized void releaseIterator(Iterator iter) { Object oIterActive = m_oIterActive; if (oIterActive instanceof WeakReference) { // there is only one active iterator, it must be this one if (((WeakReference) oIterActive).get() == iter) { m_oIterActive = null; } } else { // remove one of many active iterators ((Map) oIterActive).remove(iter); // once we go to an Iterator Map we don't switch back; the // assumption is that if multiple concurrent iterators are // allocated once, they will be allocated again, so avoid // thrashing on frequent WeakHashMap creation } } /** * Determine if there are any active Iterators, which may mean that they * are in the middle of iterating over the Map. * * @return true iff there is at least one active Iterator */ protected synchronized boolean isActiveIterator() { Object oIterActive = m_oIterActive; if (oIterActive == null) { return false; } else if (oIterActive instanceof WeakReference) { return ((WeakReference) oIterActive).get() != null; } else { return !((Map) oIterActive).isEmpty(); } } /** * Attempt to lock the segment corresponding to the specified bucket. * * @param nBucket the bucket index * * @return true iff the segment was successfully locked */ protected boolean lockBucket(int nBucket) { return lockSegment(getSegmentIndex(nBucket), true); } /** * Attempt to lock the specified segment. * * @param nSegment the segment to lock * @param fBlock should we block on trying to lock the segment * * @return true iff the segment was successfully locked */ protected boolean lockSegment(int nSegment, boolean fBlock) { long maskLock = 1L << nSegment; AtomicLong atomicLocks = m_atomicLocks; // optimization: assume nothing is already locked if (atomicLocks.compareAndSet(LOCKS_NONE, maskLock)) { // optimistic case worked; we locked the segment return true; } // check to see if everything is locked or a lock-all is pending, or // if the desired segment is locked for (int cAttempts = 0; true;) { long lLocks = atomicLocks.get(); if ((lLocks & (LOCK_ALL_PENDING | maskLock)) != 0L) { if (fBlock) { // spin a few times before waiting if ((++cAttempts % SEGMENT_LOCK_MAX_SPIN) == 0) { contendForSegment(nSegment); } continue; } else { return false; } } if (atomicLocks.compareAndSet(lLocks, lLocks | maskLock)) { return true; } } } /** * Unlock the segment corresponding to the specified bucket that was * previously locked using the {@link #lockBucket} method. * * @param nBucket the bucket to unlock */ protected void unlockBucket(int nBucket) { unlockSegment(getSegmentIndex(nBucket)); } /** * Unlock the specified segment previously locked using the {@link * #lockSegment} method. * * @param nSegment the segment to unlock */ protected void unlockSegment(int nSegment) { long maskLock = 1L << nSegment; AtomicLong atomicLocks = m_atomicLocks; long lLocks = maskLock; // make an optimistic attempt (first loop-iteration) in assuming that // nothing other than nSegment is locked while (!atomicLocks.compareAndSet(lLocks, lLocks & ~maskLock)) { lLocks = atomicLocks.get(); } // Check to see if there are other threads contending for the segment // that we just unlocked that need to be notified Segment segment = m_aSegment[nSegment]; if (segment.fContend) { synchronized (segment) { if (segment.fContend) { segment.fContend = false; segment.notifyAll(); } } } } /** * Wait for a segment to be unlocked. * * @param nSegment the segment-lock to be waited for */ protected void contendForSegment(int nSegment) { long maskLock = 1L << nSegment; Segment segment = m_aSegment[nSegment]; synchronized (segment) { // set the contended bit for the segment segment.fContend = true; // check that the segment is still locked if ((m_atomicLocks.get() & maskLock) == 0) { // The segment was unlocked; try to lock again. return; } // At this point, we know the lock and contend bits are set, so we // know that there is a segment-lock holder that should notify us // when they unlock it. try { Blocking.wait(segment, 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw Base.ensureRuntimeException(e, "Segment lock interrupted"); } } } /** * Lock everything. This method will not return until the lock is placed. * It must not be called on a thread that could already hold a lock. */ protected void lockAllBuckets() { int cAttempts = 0; while (!lockAllSegments(LOCKS_NONE)) { // wait for (possible) resize to finish getStableBucketArray(); // spin a few times before waiting if ((++cAttempts % SEGMENT_LOCK_MAX_SPIN) == 0) { contendForSegment(LOCK_ALL_PENDING_IDX); } } } /** * Lock everything, assuming that the segment for the specified bucket has * already been locked. * * @param nBucketAlreadyLocked the bucket that was already locked. * * @return false if the operation failed because another thread * was also trying to lock everything (indicating potential * deadlock) */ protected boolean lockAllBuckets(int nBucketAlreadyLocked) { return lockAllSegments(1L << (getSegmentIndex(nBucketAlreadyLocked))); } /** * Unlock everything. */ protected void unlockAllBuckets() { unlockAllSegments(LOCKS_NONE); } /** * Unlock everything, leaving only the segment for the specified bucket * locked. * * @param nBucketLeaveLocked the bucket that was already locked */ protected void unlockAllBuckets(int nBucketLeaveLocked) { unlockAllSegments(1L << (getSegmentIndex(nBucketLeaveLocked))); } /** * Lock all segments except for the specified segments that have already * been locked by the calling thread. * * @param lLocksHeld the bit-mask representing all segment-locks that the * calling thread already holds * * @return false if the operation failed because another thread was also * trying to lock everything (indicating potential deadlock) */ protected boolean lockAllSegments(long lLocksHeld) { AtomicLong atomicLocks = m_atomicLocks; // the passed-in mask must already be locked /* Base.azzert(lLocksHeld == LOCKS_NONE || (atomicLocks.getCount() & lLocksHeld) == lLocksHeld); */ // optimistic attempt: lock everything if (atomicLocks.compareAndSet(lLocksHeld, LOCKS_ALL)) { // everything else was available and is now locked return true; } // pessimistic attempt: attempt to lock the "intent" lock while (true) { long lLocks = atomicLocks.get(); if ((lLocks & LOCK_ALL_PENDING) != 0L) { return false; } if (atomicLocks.compareAndSet(lLocks, lLocks | LOCK_ALL_PENDING)) { lLocksHeld |= LOCK_ALL_PENDING; break; } } // lock all remaining unlocked segments int cAttempts = 0; while (lLocksHeld != LOCKS_ALL) { // find what locks are held in total by this and all other threads long lLocks = atomicLocks.get(); // request all the locks that are not currently held by any thread if (atomicLocks.compareAndSet(lLocks, LOCKS_ALL)) { // we just locked all of the available segment-locks; continue // until we have locked all of the segment-locks. lLocksHeld |= ~lLocks; } else { // occasionally stop spinning and yield if ((++cAttempts % SEGMENT_LOCK_MAX_SPIN) == 0) { Thread.yield(); } } } return true; } /** * Unlock all segments, except the segment-locks indicated by the specified * bit-vector. This method must only be called by a thread if that thread * has successfully called {@link #lockAllSegments}. * * @param lLocksKeep the segment-locks to keep locked */ protected void unlockAllSegments(long lLocksKeep) { if (!m_atomicLocks.compareAndSet(LOCKS_ALL, lLocksKeep)) { throw new IllegalStateException(); } // check to see if any threads are contending for segment-locks that // we just unlocked (or the lock-all intention bit). Segment[] aSegment = m_aSegment; for (int i = 0; i < LOCK_COUNT; i++) { long maskLock = 1L << i; Segment segment = aSegment[i]; // if we are not keeping this segment-lock and it is being waited // on, we need to notify the waiters if ((lLocksKeep & maskLock) == 0 && segment.fContend) { synchronized (segment) { if (segment.fContend) { segment.fContend = false; segment.notifyAll(); } } } } } // ----- inner interface: EntryAction ----------------------------------- /** * An EntryAction encapsulates a logical action to be executed in the * context of a key (that may or may not exist in the map). If an Entry * exists in the map for the key, invokeFound is called; otherwise * invokeNotFound is called. *

* EntryAction instances are state-aware and are invoked with an opaque * context object supplied by the action client. The meanings of the * supplied context and return-values are polymorphic in EntryAction type. * Depending on the EntryAction type, it may or may not be invoked while * holding the segment lock for the key. *

* An EntryAction should not acquire additional segment-locks. */ protected static interface EntryAction { /** * Invoke some action, holding the segment lock, when a matching Entry * is found. * * @param oKey the key to which the action is applied * @param oContext opaque context specific to the action * @param aeBucket the bucket array * @param nBucket the index into the bucket array * @param entryPrev the Entry object immediately preceding the * Entry that was found, or null * @param entryCur the Entry object that was found * * @return an opaque result value */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur); /** * Invoke some action, holding the segment lock, when no matching Entry * is found. * * @param oKey the key to which the action is applied * @param oContext opaque context specific to the action * @param aeBucket the bucket array * @param nBucket the index into the bucket array * * @return an opaque result value */ public Object invokeNotFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket); } // ----- inner interface: IterableEntryAction --------------------------- /** * IterableEntryAction is an EntryAction that is suitable for applying to * all keys in a map. */ protected static interface IterableEntryAction extends EntryAction { /** * Return true iff further key evaluations for the given context are * known to be unnecessary. * * @param oContext the action context * * @return true iff further evaluations are unnecessary */ public boolean isComplete(Object oContext); } // ----- inner class: EntryActionAdapter -------------------------------- /** * EntryActionAdapter is a convenience class that provides default * implementations for the EntryAction and IterableEntryAction interface * methods. */ protected static abstract class EntryActionAdapter implements EntryAction, IterableEntryAction { /** * {@inheritDoc} */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { return NO_VALUE; } /** * {@inheritDoc} */ public Object invokeNotFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket) { return NO_VALUE; } /** * {@inheritDoc} */ public boolean isComplete(Object oContext) { return false; } } // ----- inner class: GetEntryAction ------------------------------------ /** * Factory for GetEntryAction * * @return a GetEntryAction */ protected GetEntryAction instantiateGetEntryAction() { return new GetEntryAction(); } /** * Action support for getEntryInternal. The action performs an Entry * lookup by key and is not required to run while holding segment locks. *

* The context object for a GetEntryAction is either Boolean.TRUE * or Boolean.FALSE indicating whether or not to return synthetic * entries. *

* The result of invoking a GetEntryAction is the (possibly synthetic) * Entry corresponding to a given key, null if no matching Entry * is found, or NO_VALUE indicating that a concurrent resize * occurred, and the operation must be repeated. */ protected class GetEntryAction implements EntryAction { /** * {@inheritDoc} */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { // An Entry object was found. If synthetic entries are to be // returned, or the found entry is not synthetic, then return it. boolean fSynthetic = oContext == Boolean.TRUE; return (fSynthetic || !entryCur.isSynthetic()) ? entryCur : null; } /** * {@inheritDoc} */ public Object invokeNotFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket) { // If a resize occurred, the oKey may be in the map and was // reshuffled while we were looking for it; we know a resize // occurred iff the hash bucket array changed, in which case we // must return NO_VALUE so that the lookup is retried. return aeBucket == m_aeBucket ? null : NO_VALUE; } } // ----- inner class: InsertAction -------------------------------------- /** * Factory for InsertAction * * @return an InsertAction */ protected InsertAction instantiateInsertAction() { return new InsertAction(); } /** * Action support for insert. The action performs locked insert, and is * expected to run while holding the segment-lock for the specified key. *

* The context object for an InsertAction is the value object to insert in * the map, or NO_VALUE to insert a synthetic Entry. *

* The result of invoking an InsertAction is the (possibly synthetic) Entry * object that was inserted for the specified key, or NO_VALUE * indicating that a mapping for the key already exists in the map. */ protected class InsertAction implements EntryAction { /** * {@inheritDoc} */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { if (entryCur.isSynthetic()) { // logical insert entryCur.setValueInternal(oContext); // return the (logically) newly inserted entry return entryCur; } // An entry was found under put(); return NO_VALUE return NO_VALUE; } /** * {@inheritDoc} */ public Object invokeNotFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket) { // No entry object was found under put(); having locked the // segment, we are now free to insert a new Entry. Object oValue = oContext; int nHash = (oKey == null ? 0 : oKey.hashCode()); // instantiate and configure a new Entry Entry entry = instantiateEntry(oKey, oValue, nHash); // put the Entry in at the front of the list of entries for that // bucket entry.setNext(aeBucket[nBucket]); aeBucket[nBucket] = entry; // increment the entry counter for the segment ++m_aSegment[getSegmentIndex(nBucket)].cEntries; // return the newly created entry return entry; } } // ----- inner class: RemoveAction -------------------------------------- /** * Factory for RemoveAction * * @return a RemoveAction */ protected RemoveAction instantiateRemoveAction() { return new RemoveAction(); } /** * Action support for remove(). The action performs a locked remove, and is * expected to run while holding the segment-lock for the specified key. *

* The context object for a RemoveAction is unused. *

* The result of invoking a RemoveAction is the previous value associated * with the specified key, or NO_VALUE if no mapping for the key * exists in the map. Note that a synthetic Entry does not represent a * key-value mapping, so NO_VALUE is returned if a matching * synthetic Entry is found. */ protected class RemoveAction extends EntryActionAdapter { /** * {@inheritDoc} */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { // Remove the entry from the bucket-list Entry entryNext = entryCur.nextEntry(true); if (entryPrev == null) { aeBucket[nBucket] = entryNext; } else { entryPrev.setNext(entryNext); } --m_aSegment[getSegmentIndex(nBucket)].cEntries; return entryCur.getValueInternal(); } } // ----- inner class: ContainsValueAction ------------------------------- /** * Factory for ContainsValueAction * * @return a ContainsValueAction */ protected ContainsValueAction instantiateContainsValueAction() { return new ContainsValueAction(); } /** * Action support for containsValue(). The action performs a lookup by * value and is not required to run while holding any segment-locks. *

* The context object for a ContainsValueAction is an opaque context * created by instantiateContext. *

* The result of invoking a ContainsValueAction is Boolean.TRUE if * the value is found in the map or Boolean.FALSE if the value is * not found in the map. */ protected static class ContainsValueAction extends EntryActionAdapter { /** * {@inheritDoc} */ public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { ContainsValueContext context = (ContainsValueContext) oContext; if (Base.equals(entryCur.getValue(), context.m_oValue)) { context.m_fFound = true; } return NO_VALUE; } /** * {@inheritDoc} */ public boolean isComplete(Object oContext) { return isFound(oContext); } /** * Return true iff the value was found * * @param oContext the ContainsValueContext object * * @return true iff the value was found */ public boolean isFound(Object oContext) { return ((ContainsValueContext) oContext).m_fFound; } /** * Instantiate a context appropriate for applying ContainsValueAction * to lookup oValue in the map. * * @param oValue the value to test the existence of * * @return a context to use with a ContainsValueAction */ public Object instantiateContext(Object oValue) { ContainsValueContext oContext = new ContainsValueContext(); oContext.m_oValue = oValue; return oContext; } /** * Context for ContainsValueAction. */ private static class ContainsValueContext { /** * Has the value been found? */ private boolean m_fFound; /** * The value being searched for. */ private Object m_oValue; } } // ----- inner class: Segment ------------------------------------------- /** * Segment metadata. */ protected static class Segment { /** * The number of Entry objects (including synthetics) in this segment. *

* Note: On a JSR-133 non-compliant JVM, this value may be stale to * some extent. It is only used as an internal growth statistic, so * this is acceptable. On JSR-133 compliant JVMs, the segment lock * under which this value is modified performs an atomic CAS which * would guarantee that writes get flushed. */ protected int cEntries; /** * Are any threads contending to lock this segment? */ protected volatile boolean fContend; } // ----- inner class: Entry --------------------------------------------- /** * Factory for Entry. * * @param oKey the key * @param oValue the value * @param nHash the hashCode value of the key * * @return a new instance of the Entry class (or a subclass thereof) */ protected Entry instantiateEntry(Object oKey, Object oValue, int nHash) { return new Entry(oKey, oValue, nHash); } /** * Return the first non-synthetic Entry object contained by in the * specified bucket. * * @param aeBucket the array of hash buckets * @param nBucket the bucket index * * @return the first non-synthetic Entry in the specified bucket or null */ protected static Entry entryFromBucket(Entry[] aeBucket, int nBucket) { Entry entry = aeBucket[nBucket]; return (entry != null && entry.isSynthetic()) ? entry.nextEntry() : entry; } /** * A map entry (key-value pair). The Map.entrySet method returns * a collection-view of the map, whose elements are of this class. */ protected static class Entry extends Base implements Map.Entry { /** * Construct an Entry object with the specified key, value and hash. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * @param nHash the hashCode for the specified key */ protected Entry(Object oKey, Object oValue, int nHash) { m_oKey = oKey; m_oValue = oValue; m_nHash = nHash; } /** * Returns the key corresponding to this entry. * * @return the key corresponding to this entry */ public Object getKey() { return m_oKey; } /** * Returns the value corresponding to this entry. If the mapping * has been removed from the backing map (by the iterator's * remove operation), the results of this call are undefined. * * @return the value corresponding to this entry */ public Object getValue() { Object oValue = m_oValue; return oValue == SegmentedHashMap.NO_VALUE ? null : oValue; } /** * Returns the value corresponding to this entry, or NO_VALUE * if this Entry is synthetid. If the mapping has been removed from * the backing map (by the iterator's remove operation), the * results of this call are undefined. * * @return the value corresponding to this entry, or NO_VALUE * if the Entry is synthetic */ protected Object getValueInternal() { return m_oValue; } /** * Replaces the value corresponding to this entry with the specified * value (writes through to the map). The behavior of this call is * undefined if the mapping has already been removed from the map (by * the iterator's remove operation). * * @param oValue new value to be stored in this entry * * @return old value corresponding to the entry */ public Object setValue(Object oValue) { Object oPrev = setValueInternal(oValue); return oPrev == SegmentedHashMap.NO_VALUE ? null : oPrev; } /** * Replaces the value corresponding to this entry with the specified * value (writes through to the map). The behavior of this call is * undefined if the mapping has already been removed from the map. * * @param oValue new value to be stored in this entry * * @return old value corresponding to the entry, or NO_VALUE * if the Entry was synthetic */ protected Object setValueInternal(Object oValue) { Object oPrev = m_oValue; m_oValue = oValue; return oPrev; } /** * Compares the specified object with this entry for equality. * Returns true if the given object is also a map entry and * the two entries represent the same mapping. More formally, two * entries e1 and e2 represent the same mapping * if: *

        *     (e1.getKey()==null ?
        *      e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
        *     (e1.getValue()==null ?
        *      e2.getValue()==null : e1.getValue().equals(e2.getValue()))
        * 
* This ensures that the equals method works properly across * different implementations of the Map.Entry interface. * * @param o object to be compared for equality with this map entry * * @return true if the specified object is equal to this map * entry */ public boolean equals(Object o) { if (o instanceof Map.Entry) { Map.Entry that = (Map.Entry) o; return this == that || Base.equals(this.getKey(), that.getKey()) && Base.equals(this.getValue(), that.getValue()); } return false; } /** * Returns the hash code value for this map entry. The hash code * of a map entry e is defined to be: *
        *     (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
        *     (e.getValue()==null ? 0 : e.getValue().hashCode())
        * 
* This ensures that e1.equals(e2) implies that * e1.hashCode()==e2.hashCode() for any two Entries * e1 and e2, as required by the general contract of * Object.hashCode. * * @return the hash code value for this map entry */ public int hashCode() { Object oKey = m_oKey; Object oValue = getValue(); return (oKey == null ? 0 : m_nHash) ^ (oValue == null ? 0 : oValue.hashCode()); } /** * Render the map entry as a String. * * @return the details about this entry */ public String toString() { return "key=\"" + getKey() + '\"' + ", value=\"" + getValue() + '\"'; } /** * Is this Entry synthetic? * * @return true iff this Entry is synthetic */ protected boolean isSynthetic() { return false; } /** * Set the next entry in the linked list (open hash) * * @param eNext the next Entry */ protected void setNext(Entry eNext) { m_eNext = eNext; } /** * Get the next non-synthetic entry in the linked list (open hash) * * @return the next non-synthetic entry in the linked list */ protected Entry nextEntry() { return nextEntry(false); } /** * Get the next entry in the linked list (open hash). If * fSynthetic is specified, also return synthetic Entry * objects. Synethetic entries are Entry objects logically associated * with a given key, but do not represent a key-value mapping in this * map. * * @param fSynthetic include synthetic Entry objects? * * @return the next Entry in the linked list */ protected Entry nextEntry(boolean fSynthetic) { Entry eNext = m_eNext; while (eNext != null && !fSynthetic && eNext.isSynthetic()) { eNext = eNext.m_eNext; } return eNext; } // ----- data members ----------------------------------------------- /** * The key. This object reference will not change for the life of * the Entry. */ protected final Object m_oKey; /** * The value. This object reference can change within the life of the * Entry. This field is declared volatile so that get() and put() can * proceed without performing any synchronization. */ protected volatile Object m_oValue; /** * The key's hash code. This value will not change for the life of the * Entry. */ protected final int m_nHash; /** * The next entry in the linked list (an open hashing implementation). * This field is declared volatile so the entry iterator can safely * operate without performing any synchronization. */ protected volatile Entry m_eNext; } // ----- inner class: EntrySet ------------------------------------------ /** * Factory for EntrySet * * @return a new instance of the EntrySet class (or a subclass thereof) */ protected EntrySet instantiateEntrySet() { return new EntrySet(); } /** * A set of entries backed by this map. */ protected class EntrySet extends AbstractSet { // ----- Set interface ------------------------------------------ /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ public Iterator iterator() { return SegmentedHashMap.this.isEmpty() ? NullImplementation.getIterator() : instantiateIterator(); } /** * Returns the number of elements in this collection. If the collection * contains more than Integer.MAX_VALUE elements, returns * Integer.MAX_VALUE. * * @return the number of elements in this collection */ public int size() { return SegmentedHashMap.this.size(); } /** * Returns true if this collection contains the specified * element. More formally, returns true if and only if this * collection contains at least one element e such that * (o==null ? e==null : o.equals(e)). * * @param o object to be checked for containment in this collection * * @return true if this collection contains the specified * element */ public boolean contains(Object o) { if (o instanceof Map.Entry) { Map.Entry thatEntry = (Map.Entry) o; Map.Entry thisEntry = SegmentedHashMap.this.getEntryInternal(thatEntry.getKey()); return thisEntry != null && thisEntry.equals(thatEntry); } return false; } /** * Removes the specified element from this Set of entries if it is * present by removing the associated entry from the underlying * Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { if (contains(o)) { SegmentedHashMap.this.remove(((Map.Entry) o).getKey()); return true; } else { return false; } } /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { SegmentedHashMap.this.clear(); } /** * Returns an array containing all of the elements in this collection. * If the collection makes any guarantees as to what order its elements * are returned by its iterator, this method must return the elements * in the same order. The returned array will be "safe" in that no * references to it are maintained by the collection. (In other words, * this method must allocate a new array even if the collection is * backed by an Array). The caller is thus free to modify the returned * array. * * @return an array containing all of the elements in this collection */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the elements in this collection. If the * collection fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection. *

* If the collection fits in the specified array with room to spare * (i.e., the array has more elements than the collection), the element * in the array immediately following the end of the collection is set * to null. This is useful in determining the length of the * collection only if the caller knows that the collection does * not contain any null elements.) * * @param ao the array into which the elements of the collection are to * be stored, if it is big enough; otherwise, a new array of * the same runtime type is allocated for this purpose * * @return an array containing the elements of the collection * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this collection */ public Object[] toArray(Object[] ao) { SegmentedHashMap map = SegmentedHashMap.this; IterableEntryAction actionEntry = new EntryActionAdapter() { public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { if (!entryCur.isSynthetic()) { ((List) oContext).add(entryCur); } return NO_VALUE; } }; return map.toArrayInternal(actionEntry, ao); } // ----- inner class: Entry Set Iterator ------------------------ /** * Factory for EntrySetIterator. * * @return a new instance of an Iterator over the EntrySet */ protected Iterator instantiateIterator() { return new EntrySetIterator(); } /** * An Iterator over the EntrySet that is backed by the SegmentedHashMap. */ protected class EntrySetIterator extends AbstractStableIterator { // ----- constructors ----------------------------------- /** * Construct an Iterator over the Entries in the SegmentedHashMap. */ protected EntrySetIterator() { } // ----- internal ------------------------------------------- /** * Advance to the next object in the iteration. */ protected void advance() { if (m_fDeactivated) { // the Iterator has already reached the end on a previous // call to advance() return; } Entry[] aeBucket = this.m_aeBucket; if (aeBucket == null) { // Activate the iterator upon its first use. The map will // not grow while there are any active iterators. SegmentedHashMap map = SegmentedHashMap.this; map.iteratorActivated(this); aeBucket = this.m_aeBucket = map.getStableBucketArray(); } Entry entry = m_entryPrev; int iBucket = -1; // -1 indicates no change int cBuckets = aeBucket.length; while (true) { if (entry != null) { // advance within the currrent bucket entry = entry.nextEntry(); } // check if the current bucket has been exhausted, and if // it has, then advance to the first non-empty bucket if (entry == null) { iBucket = m_iBucket; do { if (++iBucket >= cBuckets) { // the iterator has been exhausted deactivate(); return; } entry = entryFromBucket(aeBucket, iBucket); } while (entry == null); } // update the current bucket index if the iterator // advanced to a new bucket if (iBucket >= 0) { m_iBucket = iBucket; } // remember the entry being iterated next m_entryPrev = entry; // report back the actual entry that exists in the Map // that is being iterated next setNext(entry); return; } } /** * Remove the specified item from the underlying Map. * * @param oPrev the previously iterated object to remove */ protected void remove(Object oPrev) { SegmentedHashMap.this.remove(((Map.Entry) oPrev).getKey()); } // ----- internal --------------------------------------- /** * Shut down the Iterator. This is done on exhaustion of the * contents of the Iterator, or on finalization of the Iterator. */ protected void deactivate() { if (!m_fDeactivated) { // No more entries to iterate; notify the containing Map // that this Iterator is no longer active. SegmentedHashMap.this.releaseIterator(this); m_fDeactivated = true; // clean up references (no longer needed) m_aeBucket = null; m_entryPrev = null; } } // ----- data members ----------------------------------- /** * Array of buckets in the underlying map. */ private Entry[] m_aeBucket; /** * Current bucket being iterated. */ private int m_iBucket = -1; /** * The most recent Entry object internally iterated. */ private Entry m_entryPrev; /** * Set to true when the Iterator is complete. */ private boolean m_fDeactivated; } } // ----- inner class: KeySet -------------------------------------------- /** * Factory for KeySet. * * @return a new instance of the KeySet class (or subclass thereof) */ protected KeySet instantiateKeySet() { return new KeySet(); } /** * A set of entries backed by this map. */ protected class KeySet extends AbstractSet { // ----- Set interface ------------------------------------------ /** * Obtain an iterator over the keys in the Map. * * @return an Iterator that provides a live view of the keys in the * underlying Map object */ public Iterator iterator() { return new Iterator() { private Iterator m_iter = SegmentedHashMap.this.entrySet().iterator(); public boolean hasNext() { return m_iter.hasNext(); } public Object next() { return ((Map.Entry) m_iter.next()).getKey(); } public void remove() { m_iter.remove(); } }; } /** * Determine the number of keys in the Set. * * @return the number of keys in the Set, which is the same as the * number of entries in the underlying Map */ public int size() { return SegmentedHashMap.this.size(); } /** * Determine if a particular key is present in the Set. * * @return true iff the passed key object is in the key Set */ public boolean contains(Object oKey) { return SegmentedHashMap.this.containsKey(oKey); } /** * Removes the specified element from this Set of keys if it is present * by removing the associated entry from the underlying Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { SegmentedHashMap map = SegmentedHashMap.this; if (map.containsKey(o)) { map.remove(o); return true; } else { return false; } } /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { SegmentedHashMap.this.clear(); } /** * Returns an array containing all of the keys in this set. * * @return an array containing all of the keys in this set */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the keys in this Set. If the Set fits in * the specified array, it is returned therein. Otherwise, a new array * is allocated with the runtime type of the specified array and the * size of this collection. *

* If the Set fits in the specified array with room to spare (i.e., the * array has more elements than the Set), the element in the array * immediately following the end of the Set is set to null. * This is useful in determining the length of the Set only if * the caller knows that the Set does not contain any null * keys.) * * @param a the array into which the elements of the Set are to * be stored, if it is big enough; otherwise, a new array * of the same runtime type is allocated for this purpose * * @return an array containing the elements of the Set * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this Set of keys */ public Object[] toArray(Object a[]) { SegmentedHashMap map = SegmentedHashMap.this; IterableEntryAction actionEntry = new EntryActionAdapter() { public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { if (!entryCur.isSynthetic()) { ((List) oContext).add(entryCur.getKey()); } return NO_VALUE; } }; return map.toArrayInternal(actionEntry, a); } } // ----- inner class: ValuesCollection ---------------------------------- /** * Factory for ValuesCollection. * * @return a new instance of the ValuesCollection class (or subclass * thereof) */ protected ValuesCollection instantiateValuesCollection() { return new ValuesCollection(); } /** * A collection of values backed by this map. */ protected class ValuesCollection extends AbstractCollection { // ----- Collection interface ----------------------------------- /** * Obtain an iterator over the values in the Map. * * @return an Iterator that provides a live view of the values in the * underlying Map object */ public Iterator iterator() { return new Iterator() { private Iterator m_iter = SegmentedHashMap.this.entrySet().iterator(); public boolean hasNext() { return m_iter.hasNext(); } public Object next() { return ((Entry) m_iter.next()).getValue(); } public void remove() { m_iter.remove(); } }; } /** * Determine the number of values in the Collection. * * @return the number of values in the Collection, which is the same * as the number of entries in the underlying Map */ public int size() { return SegmentedHashMap.this.size(); } /** * Removes all of the elements from this Collection of values by * clearing the underlying Map. */ public void clear() { SegmentedHashMap.this.clear(); } /** * Returns an array containing all of the values in the Collection. * * @return an array containing all of the values in the Collection */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the values in the Collection. If the * Collection fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection. *

* If the Collection fits in the specified array with room to spare * (i.e., the array has more elements than the Collection), the element * in the array immediately following the end of the Collection is set * to null. This is useful in determining the length of the * Collection only if the caller knows that the Collection does * not contain any null values.) * * @param ao the array into which the elements of the Collection are * to be stored, if it is big enough; otherwise, a new * array of the same runtime type is allocated for this * purpose * * @return an array containing the elements of the Collection * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this Collection of values */ public Object[] toArray(Object ao[]) { SegmentedHashMap map = SegmentedHashMap.this; IterableEntryAction actionEntry = new EntryActionAdapter() { public Object invokeFound(Object oKey, Object oContext, Entry[] aeBucket, int nBucket, Entry entryPrev, Entry entryCur) { if (!entryCur.isSynthetic()) { ((List) oContext).add(entryCur.getValue()); } return NO_VALUE; } }; return map.toArrayInternal(actionEntry, ao); } } // ----- constants ------------------------------------------------------ /** * When resizing, the entries array is replaced with this special empty * array to signify a resize. */ private static final Entry[] NO_ENTRIES = new Entry[0]; /** * A list of possible modulos to use. */ protected static final int[] PRIME_MODULO = { 61,127,197,277,397,457,509,587,641,701,761,827,883,953,1019,1129, 1279,1427,1543,1733,1951,2143,2371,2671,2927,3253,3539,3907,4211, 4591,4973,5393,5743,6143,6619,6997,7529,8009,8423,8819,9311,9929, 10069,11087,12203,13003,14051,15017,16007,17027,18061,19013,20063, 23011,27011,30011,35023,40009,45007,50021,60013,70001,80021,90001, 100003,120011,140009,160001,180001,200003,233021,266003,300007, 350003,400009,450001,500009,550007,600011,650011,700001,800011, 850009,900001,950009,1000003,1100009,1200007,1300021,1400017, 1500007,1600033,1700021,1800017,1900009,2000003,2500009,3000017, 3500017,4000037,4500007,5000011,6000011,7000003,8000009,9000011, 10000019,12000017,14000029,16000057,18000041,20000003,25000009, 30000001,35000011,40000003,45000017,50000017,60000011,70000027, 80000023,90000049,100000007,150000001,200000033,300000007,400000009, 500000003,600000001,700000001,800000011,900000011,1000000007, 1100000009,1200000041,1300000003,1400000023,1500000001,1600000009, 1700000009,1800000011,1900000043, 2147483647 // Integer.MAX_VALUE is a prime! }; /** * Default initial size provides a prime modulo and is large enough that * resize is not immediate. (A hash map probably uses less than 128 bytes * initially.) */ public static final int DEFAULT_INITIALSIZE = PRIME_MODULO[0]; /** * Biggest possible modulo. */ protected static final int BIGGEST_MODULO = PRIME_MODULO[PRIME_MODULO.length-1]; /** * The default load factor is 100%, which means that the hash map will not * resize until there is (on average) one entry in every bucket. The cost * of scanning a linked list in a particular bucket is very low, so there * is little reason for having this value below 1.0, and the goal is * constant order access, so assuming a perfect hash this will provide the * optimal access speed relative to size. */ public static final float DEFAULT_LOADFACTOR = 1.0F; /** * Using the default growth rate, the bucket array will grow by a factor * of four. The relatively high growth rate helps to ensure less resize * operations, an important consideration in a high-concurrency map. */ public static final float DEFAULT_GROWTHRATE = 3.0F; /** * The minimum segment capacity. */ protected static final int MIN_SEGMENT_CAPACITY = 2; /** * The number of segments to partition the hash buckets into. There is a * single lock for each segment. This number is specially chosen as 61 is * the largest prime number smaller than 64 (the size of the datatype * used to represent the lock). */ protected static final int SEGMENT_COUNT = 61; /** * The number of segment-locks. Each segment has its own lock and * there is a global "intention" lock. */ protected static final int LOCK_COUNT = SEGMENT_COUNT + 1; /** * The lock representation used to indicate that no locks are set. */ protected static final long LOCKS_NONE = 0L; /** * The lock representation used to indicate that all mutexes are locked. */ protected static final long LOCKS_ALL = 0xFFFFFFFFFFFFFFFFL; /** * The mutex number used to indicate that a lock-all is pending. */ protected static final int LOCK_ALL_PENDING_IDX = 61; /** * The bit-mask used to indicate that a lock-all is pending. */ protected static final long LOCK_ALL_PENDING = 1L << LOCK_ALL_PENDING_IDX; /** * Maximum number of times to spin while trying to acquire a segment lock * before waiting. */ protected static final int SEGMENT_LOCK_MAX_SPIN = 0xF; /** * Size threshold used by the putAll operation. */ protected static final int PUTALL_THRESHOLD = (int) (1.5 * SEGMENT_COUNT); /** * Object to be used as a value representing that the Entry object is * "synthetic" and while logically associated with a key, does not * represent a key-value mapping in the Map. */ protected static final Object NO_VALUE = new Object(); // ----- data members --------------------------------------------------- /** * When resizing completes, a notification is issued against this object. */ protected final Object RESIZING = new Object(); /** * The "segment-locks". This AtomicCounter is actually used just as an * "Atomic Long" value, with each of the first 61 bits being used to * represent a segment-lock. In this case, the number of "buckets" is * fixed to 61 (the largest prime smaller than 64). */ protected final AtomicLong m_atomicLocks = new AtomicLong(); /** * An array of the control-structures for the Map's segments. */ protected final Segment[] m_aSegment; /** * The array of hash buckets. This field is declared volatile in order to * reduce synchronization. */ protected volatile Entry[] m_aeBucket; /** * The capacity of each segment (the point at which we should resize). */ protected int m_cSegmentCapacity; /** * The determining factor for the hash map capacity given a certain number * of buckets, such that capacity = bucketcount * loadfactor. */ protected final float m_flLoadFactor; /** * The rate of growth as a fraction of the current number of buckets, * 0 < n, such that the hash map grows to bucketcount * (1 + growth-rate). */ protected final float m_flGrowthRate; /** * The set of entries backed by this map. */ protected EntrySet m_setEntries; /** * The set of keys backed by this map. */ protected KeySet m_setKeys; /** * The collection of values backed by this map. */ protected ValuesCollection m_colValues; /** * A holder for active Iterator(s): either WeakReference(<Iterator>) or * WeakHashMap(<Iterator> null) */ protected Object m_oIterActive; /** * The singleton action for getEntryInternal support. */ protected GetEntryAction m_actionGetEntry; /** * The singleton action for insert support. */ protected InsertAction m_actionInsert; /** * The singleton action for remove support. */ protected RemoveAction m_actionRemove; /** * The singleton action for containsValue support. */ protected ContainsValueAction m_actionContainsValue; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy