From e70adb24864900e573828ad9256d7e74ef3d4aa7 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 2 Jun 2015 23:00:26 +0200 Subject: [PATCH] java 8 stamped lock is faster than reentrantlock. WIP caches --- .../dorkbox/util/messagebus/IMessageBus.java | 6 +- .../util/messagebus/MultiMBassador.java | 189 +- .../common/AbstractConcurrentSet.java | 27 +- .../common/ConcurrentHashMapV8.java | 8276 ++++++++--------- .../util/messagebus/common/HashMapTree.java | 22 +- .../messagebus/common/MessageHandler.java | 29 +- .../common/StrongConcurrentSet.java | 2 +- .../common/StrongConcurrentSetV8.java | 4 +- .../messagebus/common/SuperClassUtils.java | 93 + .../util/messagebus/common/VarArgUtils.java | 30 +- .../messagebus/common/WeakConcurrentSet.java | 10 +- .../dorkbox/util/messagebus/common/item3.java | 11 + .../simpleq/MpmcMultiTransferArrayQueue.java | 23 +- .../common/thread/ConcurrentSet.java | 6 +- .../util/messagebus/subscription/Matcher.java | 5 + .../messagebus/subscription/Subscription.java | 32 +- .../SubscriptionManager.java | 150 +- .../SubscriptionUtils.java | 185 +- .../messagebus/SubscriptionManagerTest.java | 163 +- .../messagebus/SynchronizedHandlerTest.java | 9 +- .../common/SubscriptionValidator.java | 49 +- .../util/messagebus/common/TestUtil.java | 57 +- 22 files changed, 4664 insertions(+), 4714 deletions(-) create mode 100644 src/main/java/dorkbox/util/messagebus/common/SuperClassUtils.java create mode 100644 src/main/java/dorkbox/util/messagebus/common/item3.java create mode 100644 src/main/java/dorkbox/util/messagebus/subscription/Matcher.java rename src/main/java/dorkbox/util/messagebus/{ => subscription}/SubscriptionManager.java (82%) rename src/main/java/dorkbox/util/messagebus/{common => subscription}/SubscriptionUtils.java (72%) diff --git a/src/main/java/dorkbox/util/messagebus/IMessageBus.java b/src/main/java/dorkbox/util/messagebus/IMessageBus.java index fe6e675..fd01d21 100644 --- a/src/main/java/dorkbox/util/messagebus/IMessageBus.java +++ b/src/main/java/dorkbox/util/messagebus/IMessageBus.java @@ -14,7 +14,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport; *

* Each message publication is isolated from all other running publications such that it does not interfere with them. * Hence, the bus generally expects message handlers to be stateless as it may invoke them concurrently if multiple - * messages get published asynchronously. If handlers are stateful and not thread-safe they can be marked to be invoked + * messages getSubscriptions published asynchronously. If handlers are stateful and not thread-safe they can be marked to be invoked * in a synchronized fashion using @Synchronized annotation * *

@@ -25,7 +25,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport; *

* By default, the bus uses weak references to all listeners such that registered listeners do not need to * be explicitly unregistered to be eligible for garbage collection. Dead (garbage collected) listeners are - * removed on-the-fly as messages get dispatched. This can be changed using the @Listener annotation. + * removed on-the-fly as messages getSubscriptions dispatched. This can be changed using the @Listener annotation. * *

* Generally message handlers will be invoked in inverse sequence of subscription but any @@ -50,7 +50,7 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport; * *

* NOTE: Generic type parameters of messages will not be taken into account, e.g. a List will - * get dispatched to all message handlers that take an instance of List as their parameter + * getSubscriptions dispatched to all message handlers that take an instance of List as their parameter * * @Author bennidi * Date: 2/8/12 diff --git a/src/main/java/dorkbox/util/messagebus/MultiMBassador.java b/src/main/java/dorkbox/util/messagebus/MultiMBassador.java index 23d5063..2d26b59 100644 --- a/src/main/java/dorkbox/util/messagebus/MultiMBassador.java +++ b/src/main/java/dorkbox/util/messagebus/MultiMBassador.java @@ -1,31 +1,29 @@ package dorkbox.util.messagebus; -import java.util.ArrayDeque; -import java.util.Collection; - -import org.jctools.util.Pow2; - import dorkbox.util.messagebus.common.DeadMessage; import dorkbox.util.messagebus.common.NamedThreadFactory; import dorkbox.util.messagebus.common.simpleq.MpmcMultiTransferArrayQueue; import dorkbox.util.messagebus.common.simpleq.MultiNode; import dorkbox.util.messagebus.error.IPublicationErrorHandler; import dorkbox.util.messagebus.error.PublicationError; +import dorkbox.util.messagebus.subscription.Matcher; import dorkbox.util.messagebus.subscription.Subscription; +import dorkbox.util.messagebus.subscription.SubscriptionManager; +import org.jctools.util.Pow2; + +import java.util.ArrayDeque; +import java.util.Collection; /** * The base class for all message bus implementations with support for asynchronous message dispatch * - * @Author bennidi * @author dorkbox, llc * Date: 2/2/15 */ public class MultiMBassador implements IMessageBus { - - public static final String ERROR_HANDLER_MSG = - "INFO: No error handler has been configured to handle exceptions during publication.\n" + - "Publication error handlers can be added by bus.addErrorHandler()\n" + - "Falling back to console logger."; + public static final String ERROR_HANDLER_MSG = "INFO: No error handler has been configured to handle exceptions during publication.\n" + + "Publication error handlers can be added by bus.addErrorHandler()\n" + + "Falling back to console logger."; // this handler will receive all errors that occur during message dispatch or message handling private final Collection errorHandlers = new ArrayDeque(); @@ -36,12 +34,7 @@ public class MultiMBassador implements IMessageBus { private final Collection threads; - /** - * if true, only exact matching will be performed on classes. Setting this to true - * removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the - * system. By default, this is FALSE, to support subTypes and VarArg matching. - */ - private final boolean forceExactMatches; + private final Matcher subscriptionMatcher; /** * Notifies the consumers during shutdown, that it's on purpose. @@ -52,7 +45,7 @@ public class MultiMBassador implements IMessageBus { * By default, will permit subTypes and VarArg matching, and will use half of CPUs available for dispatching async messages */ public MultiMBassador() { - this(Runtime.getRuntime().availableProcessors()/2); + this(Runtime.getRuntime().availableProcessors() / 2); } /** @@ -64,21 +57,37 @@ public class MultiMBassador implements IMessageBus { /** * @param forceExactMatches if TRUE, only exact matching will be performed on classes. Setting this to true - * removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the - * system. By default, this is FALSE, to support subTypes and VarArg matching. - * - * @param numberOfThreads how many threads to have for dispatching async messages + * removes the ability to have subTypes and VarArg matching, and doing so doubles the speed of the + * system. By default, this is FALSE, to support subTypes and VarArg matching. + * @param numberOfThreads how many threads to have for dispatching async messages */ public MultiMBassador(boolean forceExactMatches, int numberOfThreads) { if (numberOfThreads < 2) { numberOfThreads = 2; // at LEAST 2 threads } numberOfThreads = Pow2.roundToPowerOfTwo(numberOfThreads); - this.forceExactMatches = forceExactMatches; - this.dispatchQueue = new MpmcMultiTransferArrayQueue(numberOfThreads); - this.subscriptionManager = new SubscriptionManager(numberOfThreads); + + + if (forceExactMatches) { + subscriptionMatcher = new Matcher() { + @Override + public Subscription[] getSubscriptions(Class messageClass) { + return subscriptionManager.getSubscriptionsForcedExact(messageClass); + } + }; + } + else { + subscriptionMatcher = new Matcher() { + @Override + public Subscription[] getSubscriptions(Class messageClass) { + return subscriptionManager.getSubscriptions(messageClass); + } + }; + } + + this.threads = new ArrayDeque(numberOfThreads); NamedThreadFactory dispatchThreadFactory = new NamedThreadFactory("MessageBus"); @@ -95,33 +104,37 @@ public class MultiMBassador implements IMessageBus { while (true) { IN_QUEUE.take(node); switch (node.messageType) { - case 1: publish(node.item1); continue; - case 2: publish(node.item1, node.item2); continue; - case 3: publish(node.item1, node.item2, node.item3); continue; + case 1: + publish(node.item1); + continue; + case 2: + publish(node.item1, node.item2); + continue; + case 3: + publish(node.item1, node.item2, node.item3); + continue; } } } catch (InterruptedException e) { if (!MultiMBassador.this.shuttingDown) { switch (node.messageType) { case 1: { - handlePublicationError(new PublicationError() - .setMessage("Thread interupted while processing message") - .setCause(e) - .setPublishedObject(node.item1)); + handlePublicationError( + new PublicationError().setMessage("Thread interupted while processing message") + .setCause(e).setPublishedObject(node.item1)); continue; } case 2: { - handlePublicationError(new PublicationError() - .setMessage("Thread interupted while processing message") - .setCause(e) - .setPublishedObject(node.item1, node.item2)); + handlePublicationError( + new PublicationError().setMessage("Thread interupted while processing message") + .setCause(e).setPublishedObject(node.item1, node.item2)); continue; } case 3: { - handlePublicationError(new PublicationError() - .setMessage("Thread interupted while processing message") - .setCause(e) - .setPublishedObject(node.item1, node.item2, node.item3)); + handlePublicationError( + new PublicationError().setMessage("Thread interupted while processing message") + .setCause(e) + .setPublishedObject(node.item1, node.item2, node.item3)); continue; } } @@ -189,37 +202,35 @@ public class MultiMBassador implements IMessageBus { return this.dispatchQueue.hasPendingMessages(); } + + @Override public void publish(final Object message) { try { boolean subsPublished = false; - SubscriptionManager manager = this.subscriptionManager; - Class messageClass = message.getClass(); + final SubscriptionManager manager = this.subscriptionManager; + final Class messageClass = message.getClass(); Subscription[] subscriptions; Subscription sub; - if (this.forceExactMatches) { - subscriptions = manager.getSubscriptionsForcedExact(messageClass); - } else { - subscriptions = manager.getSubscriptions(messageClass); - } - + subscriptions = subscriptionMatcher.getSubscriptions(messageClass); + int c = 0; // Run subscriptions - int length = subscriptions.length; - if (length > 0) { - for (int i=0;i 0) { @@ -236,7 +247,7 @@ public class MultiMBassador implements IMessageBus { // if (superSubscriptions != null && !superSubscriptions.isEmpty()) { // for (iterator = superSubscriptions.iterator(); iterator.hasNext();) { // sub = iterator.next(); - // + // // // this catches all exception types // sub.publishToSubscription(this, message); // } @@ -247,21 +258,21 @@ public class MultiMBassador implements IMessageBus { // // publish to var arg, only if not already an array // if (manager.hasVarArgPossibility() && !manager.utils.isArray(messageClass)) { // Object[] asArray = null; - // + // // ConcurrentSet varargSubscriptions = manager.getVarArgSubscriptions(messageClass); // if (varargSubscriptions != null && !varargSubscriptions.isEmpty()) { // asArray = (Object[]) Array.newInstance(messageClass, 1); // asArray[0] = message; - // + // // for (iterator = varargSubscriptions.iterator(); iterator.hasNext();) { // sub = iterator.next(); // // this catches all exception types // sub.publishToSubscription(this, subsPublished, asArray); // } // } - // + // // ConcurrentSet varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass); -// // now get array based superClasses (but only if those ALSO accept vararg) +// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg) // if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) { // if (asArray == null) { // asArray = (Object[]) Array.newInstance(messageClass, 1); @@ -276,14 +287,13 @@ public class MultiMBassador implements IMessageBus { // } // } - if (!subsPublished) { + if (c == 0 && !subsPublished) { // Dead Event must EXACTLY MATCH (no subclasses) Subscription[] deadSubscriptions = manager.getSubscriptionsForcedExact(DeadMessage.class); - length = deadSubscriptions.length; - if (length > 0) { + if (deadSubscriptions != null) { DeadMessage deadMessage = new DeadMessage(message); - for (int i=0;i messageClass2 = message2.getClass(); // // StrongConcurrentSet subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2); -// BooleanHolder subsPublished = this.booleanThreadLocal.get(); +// BooleanHolder subsPublished = this.booleanThreadLocal.getSubscriptions(); // subsPublished.bool = false; // // ISetEntry current; @@ -326,7 +334,7 @@ public class MultiMBassador implements IMessageBus { // // if (!this.forceExactMatches) { // StrongConcurrentSet superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2); -// // now get superClasses +// // now getSubscriptions superClasses // if (superSubscriptions != null) { // current = superSubscriptions.head; // while (current != null) { @@ -360,7 +368,7 @@ public class MultiMBassador implements IMessageBus { // } // // StrongConcurrentSet varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1); -// // now get array based superClasses (but only if those ALSO accept vararg) +// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg) // if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) { // if (asArray == null) { // asArray = (Object[]) Array.newInstance(messageClass1, 2); @@ -380,7 +388,7 @@ public class MultiMBassador implements IMessageBus { // } else { // StrongConcurrentSet varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2); // -// // now get array based superClasses (but only if those ALSO accept vararg) +// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg) // if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) { // current = varargSuperMultiSubscriptions.head; // while (current != null) { @@ -430,7 +438,7 @@ public class MultiMBassador implements IMessageBus { // Class messageClass3 = message3.getClass(); // // StrongConcurrentSet subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2, messageClass3); -// BooleanHolder subsPublished = this.booleanThreadLocal.get(); +// BooleanHolder subsPublished = this.booleanThreadLocal.getSubscriptions(); // subsPublished.bool = false; // // ISetEntry current; @@ -451,7 +459,7 @@ public class MultiMBassador implements IMessageBus { // // if (!this.forceExactMatches) { // StrongConcurrentSet superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2, messageClass3); -// // now get superClasses +// // now getSubscriptions superClasses // if (superSubscriptions != null) { // current = superSubscriptions.head; // while (current != null) { @@ -485,7 +493,7 @@ public class MultiMBassador implements IMessageBus { // } // // StrongConcurrentSet varargSuperSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1); -// // now get array based superClasses (but only if those ALSO accept vararg) +// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg) // if (varargSuperSubscriptions != null && !varargSuperSubscriptions.isEmpty()) { // if (asArray == null) { // asArray = (Object[]) Array.newInstance(messageClass1, 3); @@ -506,7 +514,7 @@ public class MultiMBassador implements IMessageBus { // } else { // StrongConcurrentSet varargSuperMultiSubscriptions = manager.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3); // -// // now get array based superClasses (but only if those ALSO accept vararg) +// // now getSubscriptions array based superClasses (but only if those ALSO accept vararg) // if (varargSuperMultiSubscriptions != null && !varargSuperMultiSubscriptions.isEmpty()) { // current = varargSuperMultiSubscriptions.head; // while (current != null) { @@ -554,12 +562,11 @@ public class MultiMBassador implements IMessageBus { try { this.dispatchQueue.transfer(message); } catch (Exception e) { - handlePublicationError(new PublicationError() - .setMessage("Error while adding an asynchronous message") - .setCause(e) - .setPublishedObject(message)); + handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e) + .setPublishedObject(message)); } - } else { + } + else { throw new NullPointerException("Message cannot be null."); } } @@ -570,12 +577,11 @@ public class MultiMBassador implements IMessageBus { try { this.dispatchQueue.transfer(message1, message2); } catch (Exception e) { - handlePublicationError(new PublicationError() - .setMessage("Error while adding an asynchronous message") - .setCause(e) - .setPublishedObject(message1, message2)); + handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e) + .setPublishedObject(message1, message2)); } - } else { + } + else { throw new NullPointerException("Messages cannot be null."); } } @@ -586,12 +592,11 @@ public class MultiMBassador implements IMessageBus { try { this.dispatchQueue.transfer(message1, message2, message3); } catch (Exception e) { - handlePublicationError(new PublicationError() - .setMessage("Error while adding an asynchronous message") - .setCause(e) - .setPublishedObject(message1, message2, message3)); + handlePublicationError(new PublicationError().setMessage("Error while adding an asynchronous message").setCause(e) + .setPublishedObject(message1, message2, message3)); } - } else { + } + else { throw new NullPointerException("Messages cannot be null."); } } diff --git a/src/main/java/dorkbox/util/messagebus/common/AbstractConcurrentSet.java b/src/main/java/dorkbox/util/messagebus/common/AbstractConcurrentSet.java index e4f4100..2b2d6cd 100644 --- a/src/main/java/dorkbox/util/messagebus/common/AbstractConcurrentSet.java +++ b/src/main/java/dorkbox/util/messagebus/common/AbstractConcurrentSet.java @@ -4,8 +4,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; - -import dorkbox.util.messagebus.common.thread.StampedLock; +import java.util.concurrent.locks.StampedLock; abstract class pad extends item { @@ -21,15 +20,15 @@ abstract class pad extends item { * @author bennidi * Date: 2/12/12 */ -public abstract class AbstractConcurrentSet extends pad implements Set { +public abstract class AbstractConcurrentSet implements Set { private static final AtomicLong id = new AtomicLong(); private final transient long ID = id.getAndIncrement(); // Internal state protected final StampedLock lock = new StampedLock(); - private final transient Map> entries; // maintain a map of entries for O(log n) lookup + private final Map> entries; // maintain a map of entries for O(log n) lookup - volatile long y0, y1, y2, y4, y5, y6 = 7L; + public volatile Entry head; // reference to the first element volatile long z0, z1, z2, z4, z5, z6 = 7L; @@ -44,7 +43,7 @@ public abstract class AbstractConcurrentSet extends pad implements Set if (element == null) { return false; } - boolean changed = false; + boolean changed; long stamp = this.lock.readLock(); if (this.entries.containsKey(element)) { @@ -67,15 +66,11 @@ public abstract class AbstractConcurrentSet extends pad implements Set @Override public boolean contains(Object element) { - long stamp = this.lock.tryOptimisticRead(); + long stamp = this.lock.readLock(); ISetEntry entry = this.entries.get(element); - if (!this.lock.validate(stamp)) { - stamp = this.lock.readLock(); - entry = this.entries.get(element); - this.lock.unlockRead(stamp); - } + this.lock.unlockRead(stamp); return entry != null && entry.getValue() != null; } @@ -125,15 +120,11 @@ public abstract class AbstractConcurrentSet extends pad implements Set @Override public boolean remove(Object element) { StampedLock lock = this.lock; - long stamp = lock.tryOptimisticRead(); + long stamp = lock.readLock(); ISetEntry entry = this.entries.get(element); - if (!lock.validate(stamp)) { - stamp = lock.readLock(); - entry = this.entries.get(element); - lock.unlockRead(stamp); - } + lock.unlockRead(stamp); if (entry == null || entry.getValue() == null) { return false; // fast exit diff --git a/src/main/java/dorkbox/util/messagebus/common/ConcurrentHashMapV8.java b/src/main/java/dorkbox/util/messagebus/common/ConcurrentHashMapV8.java index 278c317..f6f37e1 100644 --- a/src/main/java/dorkbox/util/messagebus/common/ConcurrentHashMapV8.java +++ b/src/main/java/dorkbox/util/messagebus/common/ConcurrentHashMapV8.java @@ -5,26 +5,11 @@ */ package dorkbox.util.messagebus.common; -import java.io.ObjectStreamField; import java.io.Serializable; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.LockSupport; -import java.util.concurrent.locks.ReentrantLock; /* * Bulk operations removed in Java 6 backport. @@ -42,14 +27,14 @@ import java.util.concurrent.locks.ReentrantLock; * interoperable with {@code Hashtable} in programs that rely on its * thread safety but not on its synchronization details. * - *

Retrieval operations (including {@code get}) generally do not + *

Retrieval operations (including {@code getSubscriptions}) generally do not * block, so may overlap with update operations (including {@code put} * and {@code remove}). Retrievals reflect the results of the most * recently completed update operations holding upon their * onset. (More formally, an update operation for a given key bears a * happens-before relation with any (non-null) retrieval for * that key reporting the updated value.) For aggregate operations - * such as {@code putAll} and {@code clear}, concurrent retrievals may + * such as {@code putAll} and {@code shutdown}, concurrent retrievals may * reflect insertion or removal of only some entries. Similarly, * Iterators and Enumerations return elements reflecting the state of * the hash table at some point at or since the creation of the @@ -156,7 +141,7 @@ import java.util.concurrent.locks.ReentrantLock; * *

The concurrency properties of bulk operations follow * from those of ConcurrentHashMapV8: Any non-null result returned - * from {@code get(key)} and related access methods bears a + * from {@code getSubscriptions(key)} and related access methods bears a * happens-before relation with the associated insertion or * update. The result of any bulk operation reflects the * composition of these per-element relations (but is not @@ -220,4130 +205,4139 @@ import java.util.concurrent.locks.ReentrantLock; * @param the type of mapped values */ @SuppressWarnings("all") -public class ConcurrentHashMapV8 extends AbstractMap +public class ConcurrentHashMapV8 extends ConcurrentHashMap implements ConcurrentMap, Serializable { - private static final long serialVersionUID = 7249069246763182397L; - - /** - * An object for traversing and partitioning elements of a source. - * This interface provides a subset of the functionality of JDK8 - * java.util.Spliterator. - */ - public static interface ConcurrentHashMapSpliterator { - /** - * If possible, returns a new spliterator covering - * approximately one half of the elements, which will not be - * covered by this spliterator. Returns null if cannot be - * split. - */ - ConcurrentHashMapSpliterator trySplit(); - /** - * Returns an estimate of the number of elements covered by - * this Spliterator. - */ - long estimateSize(); - - /** Applies the action to each untraversed element */ - void forEachRemaining(Action action); - /** If an element remains, applies the action and returns true. */ - boolean tryAdvance(Action action); + public ConcurrentHashMapV8(int i, float loadFactor, int stripeSize) { + super(i, loadFactor, stripeSize); } - // Sams - /** Interface describing a void action of one argument */ - public interface Action { void apply(A a); } - /** Interface describing a void action of two arguments */ - public interface BiAction { void apply(A a, B b); } - /** Interface describing a function of one argument */ - public interface Fun { T apply(A a); } - /** Interface describing a function of two arguments */ - public interface BiFun { T apply(A a, B b); } - /** Interface describing a function mapping its argument to a double */ - public interface ObjectToDouble { double apply(A a); } - /** Interface describing a function mapping its argument to a long */ - public interface ObjectToLong { long apply(A a); } - /** Interface describing a function mapping its argument to an int */ - public interface ObjectToInt {int apply(A a); } - /** Interface describing a function mapping two arguments to a double */ - public interface ObjectByObjectToDouble { double apply(A a, B b); } - /** Interface describing a function mapping two arguments to a long */ - public interface ObjectByObjectToLong { long apply(A a, B b); } - /** Interface describing a function mapping two arguments to an int */ - public interface ObjectByObjectToInt {int apply(A a, B b); } - /** Interface describing a function mapping two doubles to a double */ - public interface DoubleByDoubleToDouble { double apply(double a, double b); } - /** Interface describing a function mapping two longs to a long */ - public interface LongByLongToLong { long apply(long a, long b); } - /** Interface describing a function mapping two ints to an int */ - public interface IntByIntToInt { int apply(int a, int b); } - - /* - * Overview: - * - * The primary design goal of this hash table is to maintain - * concurrent readability (typically method get(), but also - * iterators and related methods) while minimizing update - * contention. Secondary goals are to keep space consumption about - * the same or better than java.util.HashMap, and to support high - * initial insertion rates on an empty table by many threads. - * - * This map usually acts as a binned (bucketed) hash table. Each - * key-value mapping is held in a Node. Most nodes are instances - * of the basic Node class with hash, key, value, and next - * fields. However, various subclasses exist: TreeNodes are - * arranged in balanced trees, not lists. TreeBins hold the roots - * of sets of TreeNodes. ForwardingNodes are placed at the heads - * of bins during resizing. ReservationNodes are used as - * placeholders while establishing values in computeIfAbsent and - * related methods. The types TreeBin, ForwardingNode, and - * ReservationNode do not hold normal user keys, values, or - * hashes, and are readily distinguishable during search etc - * because they have negative hash fields and null key and value - * fields. (These special nodes are either uncommon or transient, - * so the impact of carrying around some unused fields is - * insignificant.) - * - * The table is lazily initialized to a power-of-two size upon the - * first insertion. Each bin in the table normally contains a - * list of Nodes (most often, the list has only zero or one Node). - * Table accesses require volatile/atomic reads, writes, and - * CASes. Because there is no other way to arrange this without - * adding further indirections, we use intrinsics - * (sun.misc.Unsafe) operations. - * - * We use the top (sign) bit of Node hash fields for control - * purposes -- it is available anyway because of addressing - * constraints. Nodes with negative hash fields are specially - * handled or ignored in map methods. - * - * Insertion (via put or its variants) of the first node in an - * empty bin is performed by just CASing it to the bin. This is - * by far the most common case for put operations under most - * key/hash distributions. Other update operations (insert, - * delete, and replace) require locks. We do not want to waste - * the space required to associate a distinct lock object with - * each bin, so instead use the first node of a bin list itself as - * a lock. Locking support for these locks relies on builtin - * "synchronized" monitors. - * - * Using the first node of a list as a lock does not by itself - * suffice though: When a node is locked, any update must first - * validate that it is still the first node after locking it, and - * retry if not. Because new nodes are always appended to lists, - * once a node is first in a bin, it remains first until deleted - * or the bin becomes invalidated (upon resizing). - * - * The main disadvantage of per-bin locks is that other update - * operations on other nodes in a bin list protected by the same - * lock can stall, for example when user equals() or mapping - * functions take a long time. However, statistically, under - * random hash codes, this is not a common problem. Ideally, the - * frequency of nodes in bins follows a Poisson distribution - * (http://en.wikipedia.org/wiki/Poisson_distribution) with a - * parameter of about 0.5 on average, given the resizing threshold - * of 0.75, although with a large variance because of resizing - * granularity. Ignoring variance, the expected occurrences of - * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The - * first values are: - * - * 0: 0.60653066 - * 1: 0.30326533 - * 2: 0.07581633 - * 3: 0.01263606 - * 4: 0.00157952 - * 5: 0.00015795 - * 6: 0.00001316 - * 7: 0.00000094 - * 8: 0.00000006 - * more: less than 1 in ten million - * - * Lock contention probability for two threads accessing distinct - * elements is roughly 1 / (8 * #elements) under random hashes. - * - * Actual hash code distributions encountered in practice - * sometimes deviate significantly from uniform randomness. This - * includes the case when N > (1<<30), so some keys MUST collide. - * Similarly for dumb or hostile usages in which multiple keys are - * designed to have identical hash codes or ones that differs only - * in masked-out high bits. So we use a secondary strategy that - * applies when the number of nodes in a bin exceeds a - * threshold. These TreeBins use a balanced tree to hold nodes (a - * specialized form of red-black trees), bounding search time to - * O(log N). Each search step in a TreeBin is at least twice as - * slow as in a regular list, but given that N cannot exceed - * (1<<64) (before running out of addresses) this bounds search - * steps, lock hold times, etc, to reasonable constants (roughly - * 100 nodes inspected per operation worst case) so long as keys - * are Comparable (which is very common -- String, Long, etc). - * TreeBin nodes (TreeNodes) also maintain the same "next" - * traversal pointers as regular nodes, so can be traversed in - * iterators in the same way. - * - * The table is resized when occupancy exceeds a percentage - * threshold (nominally, 0.75, but see below). Any thread - * noticing an overfull bin may assist in resizing after the - * initiating thread allocates and sets up the replacement - * array. However, rather than stalling, these other threads may - * proceed with insertions etc. The use of TreeBins shields us - * from the worst case effects of overfilling while resizes are in - * progress. Resizing proceeds by transferring bins, one by one, - * from the table to the next table. To enable concurrency, the - * next table must be (incrementally) prefilled with place-holders - * serving as reverse forwarders to the old table. 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. On average, 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 concurrently - * traversing table. Upon transfer, the old table bin contains - * only a special forwarding node (with hash field "MOVED") that - * contains the next table as its key. On encountering a - * forwarding node, access and update operations restart, using - * the new table. - * - * Each bin transfer requires its bin lock, which can stall - * waiting for locks while resizing. However, because other - * threads can join in and help resize rather than contend for - * locks, average aggregate waits become shorter as resizing - * progresses. The transfer operation must also ensure that all - * accessible bins in both the old and new table are usable by any - * traversal. This is arranged by proceeding from the last bin - * (table.length - 1) up towards the first. Upon seeing a - * forwarding node, traversals (see class Traverser) arrange to - * move to the new table without revisiting nodes. However, to - * ensure that no intervening nodes are skipped, bin splitting can - * only begin after the associated reverse-forwarders are in - * place. - * - * The traversal scheme also applies to partial traversals of - * ranges of bins (via an alternate Traverser constructor) - * to support partitioned aggregate operations. Also, read-only - * operations give up if ever forwarded to a null table, which - * provides support for shutdown-style clearing, which is also not - * currently implemented. - * - * Lazy table initialization minimizes footprint until first use, - * and also avoids resizings when the first operation is from a - * putAll, constructor with map argument, or deserialization. - * These cases attempt to override the initial capacity settings, - * but harmlessly fail to take effect in cases of races. - * - * The element count is maintained using a specialization of - * LongAdder. We need to incorporate a specialization rather than - * just use a LongAdder in order to access implicit - * contention-sensing that leads to creation of multiple - * CounterCells. The counter mechanics avoid contention on - * updates but can encounter cache thrashing if read too - * frequently during concurrent access. To avoid reading so often, - * resizing under contention is attempted only upon adding to a - * bin already holding two or more nodes. Under uniform hash - * distributions, the probability of this occurring at threshold - * is around 13%, meaning that only about 1 in 8 puts check - * threshold (and after resizing, many fewer do so). - * - * TreeBins use a special form of comparison for search and - * related operations (which is the main reason we cannot use - * existing collections such as TreeMaps). TreeBins contain - * Comparable elements, but may contain others, as well as - * elements that are Comparable but not necessarily Comparable for - * the same T, so we cannot invoke compareTo among them. To handle - * this, the tree is ordered primarily by hash value, then by - * Comparable.compareTo order if applicable. On lookup at a node, - * if elements are not comparable or compare as 0 then both left - * and right children may need to be searched in the case of tied - * hash values. (This corresponds to the full list search that - * would be necessary if all elements were non-Comparable and had - * tied hashes.) On insertion, to keep a total ordering (or as - * close as is required here) across rebalancings, we compare - * classes and identityHashCodes as tie-breakers. The red-black - * balancing code is updated from pre-jdk-collections - * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) - * based in turn on Cormen, Leiserson, and Rivest "Introduction to - * Algorithms" (CLR). - * - * TreeBins also require an additional locking mechanism. While - * list traversal is always possible by readers even during - * updates, tree traversal is not, mainly because of tree-rotations - * that may change the root node and/or its linkages. TreeBins - * include a simple read-write lock mechanism parasitic on the - * main bin-synchronization strategy: Structural adjustments - * associated with an insertion or removal are already bin-locked - * (and so cannot conflict with other writers) but must wait for - * ongoing readers to finish. Since there can be only one such - * waiter, we use a simple scheme using a single "waiter" field to - * block writers. However, readers need never block. If the root - * lock is held, they proceed along the slow traversal path (via - * next-pointers) until the lock becomes available or the list is - * exhausted, whichever comes first. These cases are not fast, but - * maximize aggregate expected throughput. - * - * Maintaining API and serialization compatibility with previous - * versions of this class introduces several oddities. Mainly: We - * leave untouched but unused constructor arguments refering to - * concurrencyLevel. We accept a loadFactor constructor argument, - * but apply it only to initial table capacity (which is the only - * time that we can guarantee to honor it.) We also declare an - * unused "Segment" class that is instantiated in minimal form - * only when serializing. - * - * Also, solely for compatibility with previous versions of this - * class, it extends AbstractMap, even though all of its methods - * are overridden, so it is just useless baggage. - * - * This file is organized to make things a little easier to follow - * while reading than they might otherwise: First the main static - * declarations and utilities, then fields, then main public - * methods (with a few factorings of multiple public methods into - * internal ones), then sizing methods, trees, traversers, and - * bulk operations. - */ - - /* ---------------- Constants -------------- */ - - /** - * The largest possible table capacity. This value must be - * exactly 1<<30 to stay within Java array allocation and indexing - * bounds for power of two table sizes, and is further required - * because the top two bits of 32bit hash fields are used for - * control purposes. - */ - private static final int MAXIMUM_CAPACITY = 1 << 30; - - /** - * The default initial table capacity. Must be a power of 2 - * (i.e., at least 1) and at most MAXIMUM_CAPACITY. - */ - private static final int DEFAULT_CAPACITY = 16; - - /** - * The largest possible (non-power of two) array size. - * Needed by toArray and related methods. - */ - static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - - /** - * The default concurrency level for this table. Unused but - * defined for compatibility with previous versions of this class. - */ - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - - /** - * The load factor for this table. Overrides of this value in - * constructors affect only the initial table capacity. The - * actual floating point value isn't normally used -- it is - * simpler to use expressions such as {@code n - (n >>> 2)} for - * the associated resizing threshold. - */ - private static final float LOAD_FACTOR = 0.75f; - - /** - * The bin count threshold for using a tree rather than list for a - * bin. Bins are converted to trees when adding an element to a - * bin with at least this many nodes. The value must be greater - * than 2, and should be at least 8 to mesh with assumptions in - * tree removal about conversion back to plain bins upon - * shrinkage. - */ - static final int TREEIFY_THRESHOLD = 8; - - /** - * The bin count threshold for untreeifying a (split) bin during a - * resize operation. Should be less than TREEIFY_THRESHOLD, and at - * most 6 to mesh with shrinkage detection under removal. - */ - static final int UNTREEIFY_THRESHOLD = 6; - - /** - * The smallest table capacity for which bins may be treeified. - * (Otherwise the table is resized if too many nodes in a bin.) - * The value should be at least 4 * TREEIFY_THRESHOLD to avoid - * conflicts between resizing and treeification thresholds. - */ - static final int MIN_TREEIFY_CAPACITY = 64; - - /** - * Minimum number of rebinnings per transfer step. Ranges are - * subdivided to allow multiple resizer threads. This value - * serves as a lower bound to avoid resizers encountering - * excessive memory contention. The value should be at least - * DEFAULT_CAPACITY. - */ - private static final int MIN_TRANSFER_STRIDE = 16; - - /* - * Encodings for Node hash fields. See above for explanation. - */ - static final int MOVED = -1; // hash for forwarding nodes - static final int TREEBIN = -2; // hash for roots of trees - static final int RESERVED = -3; // hash for transient reservations - static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash - - /** Number of CPUS, to place bounds on some sizings */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** For serialization compatibility. */ - private static final ObjectStreamField[] serialPersistentFields = { - new ObjectStreamField("segments", Segment[].class), - new ObjectStreamField("segmentMask", Integer.TYPE), - new ObjectStreamField("segmentShift", Integer.TYPE) - }; - - /* ---------------- Nodes -------------- */ - - /** - * Key-value entry. This class is never exported out as a - * user-mutable Map.Entry (i.e., one supporting setValue; see - * MapEntry below), but can be used for read-only traversals used - * in bulk tasks. Subclasses of Node with a negative hash field - * are special, and contain null keys and values (but are never - * exported). Otherwise, keys and vals are never null. - */ - static class Node implements Map.Entry { - final int hash; - final K key; - volatile V val; - volatile Node next; - - Node(int hash, K key, V val, Node next) { - this.hash = hash; - this.key = key; - this.val = val; - this.next = next; - } - - @Override - public final K getKey() { return this.key; } - @Override - public final V getValue() { return this.val; } - @Override - public final int hashCode() { return this.key.hashCode() ^ this.val.hashCode(); } - @Override - public final String toString(){ return this.key + "=" + this.val; } - @Override - public final V setValue(V value) { - throw new UnsupportedOperationException(); - } - - @Override - public final boolean equals(Object o) { - Object k, v, u; Map.Entry e; - return o instanceof Map.Entry && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - (k == this.key || k.equals(this.key)) && - (v == (u = this.val) || v.equals(u)); - } - - /** - * Virtualized support for map.get(); overridden in subclasses. - */ - Node find(int h, Object k) { - Node e = this; - if (k != null) { - do { - K ek; - if (e.hash == h && - ((ek = e.key) == k || ek != null && k.equals(ek))) { - return e; - } - } while ((e = e.next) != null); - } - return null; - } - } - - /* ---------------- Static utilities -------------- */ - - /** - * Spreads (XORs) higher bits of hash to lower and also forces top - * bit to 0. Because the table uses power-of-two masking, sets of - * hashes that vary only in bits above the current mask will - * always collide. (Among known examples are sets of Float keys - * holding consecutive whole numbers in small tables.) So we - * apply a transform that spreads the impact of higher bits - * downward. There is a tradeoff between speed, utility, and - * quality of bit-spreading. Because many common sets of hashes - * are already reasonably distributed (so don't benefit from - * spreading), and because we use trees to handle large sets of - * collisions in bins, we just XOR some shifted bits in the - * cheapest possible way to reduce systematic lossage, as well as - * to incorporate impact of the highest bits that would otherwise - * never be used in index calculations because of table bounds. - */ - static final int spread(int h) { - return (h ^ h >>> 16) & HASH_BITS; - } - - /** - * Returns a power of two table size for the given desired capacity. - * See Hackers Delight, sec 3.2 - */ - private static final int tableSizeFor(int c) { - int n = c - 1; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - return n < 0 ? 1 : n >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : n + 1; - } - - /** - * Returns x's Class if it is of the form "class C implements - * Comparable", else null. - */ - static Class comparableClassFor(Object x) { - if (x instanceof Comparable) { - Class c; Type[] ts, as; Type t; ParameterizedType p; - if ((c = x.getClass()) == String.class) { - return c; - } - if ((ts = c.getGenericInterfaces()) != null) { - for (int i = 0; i < ts.length; ++i) { - if ((t = ts[i]) instanceof ParameterizedType && - (p = (ParameterizedType)t).getRawType() == - Comparable.class && - (as = p.getActualTypeArguments()) != null && - as.length == 1 && as[0] == c) { - return c; - } - } - } - } - return null; - } - - /** - * Returns k.compareTo(x) if x matches kc (k's screened comparable - * class), else 0. - */ - @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable - static int compareComparables(Class kc, Object k, Object x) { - return x == null || x.getClass() != kc ? 0 : - ((Comparable)k).compareTo(x); - } - - /* ---------------- Table element access -------------- */ - - /* - * Volatile access methods are used for table elements as well as - * elements of in-progress next table while resizing. All uses of - * the tab arguments must be null checked by callers. All callers - * also paranoically precheck that tab's length is not zero (or an - * equivalent check), thus ensuring that any index argument taking - * the form of a hash value anded with (length - 1) is a valid - * index. Note that, to be correct wrt arbitrary concurrency - * errors by users, these checks must operate on local variables, - * which accounts for some odd-looking inline assignments below. - * Note that calls to setTabAt always occur within locked regions, - * and so in principle require only release ordering, not need - * full volatile semantics, but are currently coded as volatile - * writes to be conservative. - */ - - @SuppressWarnings("unchecked") - static final Node tabAt(Node[] tab, int i) { - return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); - } - - static final boolean casTabAt(Node[] tab, int i, - Node c, Node v) { - return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); - } - - static final void setTabAt(Node[] tab, int i, Node v) { - U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); - } - - /* ---------------- Fields -------------- */ - - /** - * The array of bins. Lazily initialized upon first insertion. - * Size is always a power of two. Accessed directly by iterators. - */ - transient volatile Node[] table; - - /** - * The next table to use; non-null only while resizing. - */ - private transient volatile Node[] nextTable; - - /** - * Base counter value, used mainly when there is no contention, - * but also as a fallback during table initialization - * races. Updated via CAS. - */ - private transient volatile long baseCount; - - /** - * Table initialization and resizing control. When negative, the - * table is being initialized or resized: -1 for initialization, - * else -(1 + the number of active resizing threads). Otherwise, - * when table is null, holds the initial table size to use upon - * creation, or 0 for default. After initialization, holds the - * next element count value upon which to resize the table. - */ - private transient volatile int sizeCtl; - - /** - * The next table index (plus one) to split while resizing. - */ - private transient volatile int transferIndex; - - /** - * The least available table index to split while resizing. - */ - private transient volatile int transferOrigin; - - /** - * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. - */ - private transient volatile int cellsBusy; - - /** - * Table of counter cells. When non-null, size is a power of 2. - */ - private transient volatile CounterCell[] counterCells; - - // views - private transient KeySetView keySet; - private transient ValuesView values; - private transient EntrySetView entrySet; - - - /* ---------------- Public operations -------------- */ - - /** - * Creates a new, empty map with the default initial table size (16). - */ public ConcurrentHashMapV8() { + super(); + ; + ; } - - /** - * Creates a new, empty map with an initial table size - * accommodating the specified number of elements without the need - * to dynamically resize. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - */ - public ConcurrentHashMapV8(int initialCapacity) { - if (initialCapacity < 0) { - throw new IllegalArgumentException(); - } - int cap = initialCapacity >= MAXIMUM_CAPACITY >>> 1 ? - MAXIMUM_CAPACITY : - tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); - this.sizeCtl = cap; - } - - /** - * Creates a new map with the same mappings as the given map. - * - * @param m the map - */ - public ConcurrentHashMapV8(Map m) { - this.sizeCtl = DEFAULT_CAPACITY; - putAll(m); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}) and - * initial table density ({@code loadFactor}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @throws IllegalArgumentException if the initial capacity of - * elements is negative or the load factor is nonpositive - * - * @since 1.6 - */ - public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, 1); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}), table - * density ({@code loadFactor}), and number of concurrently - * updating threads ({@code concurrencyLevel}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation may use this value as - * a sizing hint. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive - */ - public ConcurrentHashMapV8(int initialCapacity, - float loadFactor, int concurrencyLevel) { - if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) { - throw new IllegalArgumentException(); - } - if (initialCapacity < concurrencyLevel) - { - initialCapacity = concurrencyLevel; // as estimated threads - } - long size = (long)(1.0 + initialCapacity / loadFactor); - int cap = size >= MAXIMUM_CAPACITY ? - MAXIMUM_CAPACITY : tableSizeFor((int)size); - this.sizeCtl = cap; - } - - // Original (since JDK1.2) Map methods - - /** - * {@inheritDoc} - */ - @Override - public int size() { - long n = sumCount(); - return n < 0L ? 0 : - n > Integer.MAX_VALUE ? Integer.MAX_VALUE : - (int)n; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEmpty() { - return sumCount() <= 0L; // ignore transient negative values - } - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - * - *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code key.equals(k)}, - * then this method returns {@code v}; otherwise it returns - * {@code null}. (There can be at most one such mapping.) - * - * @throws NullPointerException if the specified key is null - */ - @Override - public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - int h = spread(key.hashCode()); - if ((tab = this.table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, n - 1 & h)) != null) { - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || ek != null && key.equals(ek)) { - return e.val; - } - } - else if (eh < 0) { - return (p = e.find(h, key)) != null ? p.val : null; - } - while ((e = e.next) != null) { - if (e.hash == h && - ((ek = e.key) == key || ek != null && key.equals(ek))) { - return e.val; - } - } - } - return null; - } - - /** - * Tests if the specified object is a key in this table. - * - * @param key possible key - * @return {@code true} if and only if the specified object - * is a key in this table, as determined by the - * {@code equals} method; {@code false} otherwise - * @throws NullPointerException if the specified key is null - */ - @Override - public boolean containsKey(Object key) { - return get(key) != null; - } - - /** - * Returns {@code true} if this map maps one or more keys to the - * specified value. Note: This method may require a full traversal - * of the map, and is much slower than method {@code containsKey}. - * - * @param value value whose presence in this map is to be tested - * @return {@code true} if this map maps one or more keys to the - * specified value - * @throws NullPointerException if the specified value is null - */ - @Override - public boolean containsValue(Object value) { - if (value == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - V v; - if ((v = p.val) == value || v != null && value.equals(v)) { - return true; - } - } - } - return false; - } - - /** - * Maps the specified key to the specified value in this table. - * Neither the key nor the value can be null. - * - *

The value can be retrieved by calling the {@code get} method - * with a key that is equal to the original key. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key} - * @throws NullPointerException if the specified key or value is null - */ - @Override - public V put(K key, V value) { - return putVal(key, value, false); - } - - /** Implementation for put and putIfAbsent */ - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) { - throw new NullPointerException(); - } - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) { - tab = initTable(); - } else if ((f = tabAt(tab, i = n - 1 & hash)) == null) { - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - { - break; // no lock when adding to empty bin - } - } - else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - V oldVal = null; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - oldVal = e.val; - if (!onlyIfAbsent) { - e.val = value; - } - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) { - p.val = value; - } - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) { - treeifyBin(tab, i); - } - if (oldVal != null) { - return oldVal; - } - break; - } - } - } - addCount(1L, binCount); - return null; - } - - /** - * Copies all of the mappings from the specified map to this one. - * These mappings replace any mappings that this map had for any of the - * keys currently in the specified map. - * - * @param m mappings to be stored in this map - */ - @Override - public void putAll(Map m) { - tryPresize(m.size()); - for (Map.Entry e : m.entrySet()) { - putVal(e.getKey(), e.getValue(), false); - } - } - - /** - * Removes the key (and its corresponding value) from this map. - * This method does nothing if the key is not in the map. - * - * @param key the key that needs to be removed - * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key} - * @throws NullPointerException if the specified key is null - */ - @Override - public V remove(Object key) { - return replaceNode(key, null, null); - } - - /** - * Implementation for the four public remove/replace methods: - * Replaces node value with v, conditional upon match of cv if - * non-null. If resulting value is null, delete. - */ - final V replaceNode(Object key, V value, Object cv) { - int hash = spread(key.hashCode()); - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0 || - (f = tabAt(tab, i = n - 1 & hash)) == null) { - break; - } else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - V oldVal = null; - boolean validated = false; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - validated = true; - for (Node e = f, pred = null;;) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - V ev = e.val; - if (cv == null || cv == ev || - ev != null && cv.equals(ev)) { - oldVal = ev; - if (value != null) { - e.val = value; - } else if (pred != null) { - pred.next = e.next; - } else { - setTabAt(tab, i, e.next); - } - } - break; - } - pred = e; - if ((e = e.next) == null) { - break; - } - } - } - else if (f instanceof TreeBin) { - validated = true; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(hash, key, null)) != null) { - V pv = p.val; - if (cv == null || cv == pv || - pv != null && cv.equals(pv)) { - oldVal = pv; - if (value != null) { - p.val = value; - } else if (t.removeTreeNode(p)) { - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - } - if (validated) { - if (oldVal != null) { - if (value == null) { - addCount(-1L, -1); - } - return oldVal; - } - break; - } - } - } - return null; - } - - /** - * Removes all of the mappings from this map. - */ - @Override - public void clear() { - long delta = 0L; // negative number of deletions - int i = 0; - Node[] tab = this.table; - while (tab != null && i < tab.length) { - int fh; - Node f = tabAt(tab, i); - if (f == null) { - ++i; - } else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - i = 0; // restart - } - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - Node p = fh >= 0 ? f : - f instanceof TreeBin ? - ((TreeBin)f).first : null; - while (p != null) { - --delta; - p = p.next; - } - setTabAt(tab, i++, null); - } - } - } - } - if (delta != 0L) { - addCount(delta, -1); - } - } - - /** - * Returns a {@link Set} view of the keys contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from this map, - * via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. It does not support the {@code add} or - * {@code addAll} operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link 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 the set view - */ - @Override - public KeySetView keySet() { - KeySetView ks; - return (ks = this.keySet) != null ? ks : (this.keySet = new KeySetView(this, null)); - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection is backed by the map, so changes to the map are - * reflected in the collection, and vice-versa. The collection - * supports element removal, which removes the corresponding - * mapping from this map, via the {@code Iterator.remove}, - * {@code Collection.remove}, {@code removeAll}, - * {@code retainAll}, and {@code clear} operations. It does not - * support the {@code add} or {@code addAll} operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link 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 the collection view - */ - @Override - public Collection values() { - ValuesView vs; - return (vs = this.values) != null ? vs : (this.values = new ValuesView(this)); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from the map, - * via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link 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 the set view - */ - @Override - public Set> entrySet() { - EntrySetView es; - return (es = this.entrySet) != null ? es : (this.entrySet = new EntrySetView(this)); - } - - /** - * Returns the hash code value for this {@link Map}, i.e., - * the sum of, for each key-value pair in the map, - * {@code key.hashCode() ^ value.hashCode()}. - * - * @return the hash code value for this map - */ - @Override - public int hashCode() { - int h = 0; - Node[] t; - if ((t = this.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - h += p.key.hashCode() ^ p.val.hashCode(); - } - } - return h; - } - - /** - * Returns a string representation of this map. The string - * representation consists of a list of key-value mappings (in no - * particular order) enclosed in braces ("{@code {}}"). Adjacent - * mappings are separated by the characters {@code ", "} (comma - * and space). Each key-value mapping is rendered as the key - * followed by an equals sign ("{@code =}") followed by the - * associated value. - * - * @return a string representation of this map - */ - @Override - public String toString() { - Node[] t; - int f = (t = this.table) == null ? 0 : t.length; - Traverser it = new Traverser(t, f, 0, f); - StringBuilder sb = new StringBuilder(); - sb.append('{'); - Node p; - if ((p = it.advance()) != null) { - for (;;) { - K k = p.key; - V v = p.val; - sb.append(k == this ? "(this Map)" : k); - sb.append('='); - sb.append(v == this ? "(this Map)" : v); - if ((p = it.advance()) == null) { - break; - } - sb.append(',').append(' '); - } - } - return sb.append('}').toString(); - } - - /** - * Compares the specified object with this map for equality. - * Returns {@code true} if the given object is a map with the same - * mappings as this map. This operation may return misleading - * results if either map is concurrently modified during execution - * of this method. - * - * @param o object to be compared for equality with this map - * @return {@code true} if the specified object is equal to this map - */ - @Override - public boolean equals(Object o) { - if (o != this) { - if (!(o instanceof Map)) { - return false; - } - Map m = (Map) o; - Node[] t; - int f = (t = this.table) == null ? 0 : t.length; - Traverser it = new Traverser(t, f, 0, f); - for (Node p; (p = it.advance()) != null; ) { - V val = p.val; - Object v = m.get(p.key); - if (v == null || v != val && !v.equals(val)) { - return false; - } - } - for (Map.Entry e : m.entrySet()) { - Object mk, mv, v; - if ((mk = e.getKey()) == null || - (mv = e.getValue()) == null || - (v = get(mk)) == null || - mv != v && !mv.equals(v)) { - return false; - } - } - } - return true; - } - - /** - * Stripped-down version of helper class used in previous version, - * declared for the sake of serialization compatibility - */ - static class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - final float loadFactor; - Segment(float lf) { this.loadFactor = lf; } - } - - /** - * Saves the state of the {@code ConcurrentHashMapV8} instance to a - * stream (i.e., serializes it). - * @param s the stream - * @throws java.io.IOException if an I/O error occurs - * @serialData - * the key (Object) and value (Object) - * for each key-value mapping, followed by a null pair. - * The key-value mappings are emitted in no particular order. - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - // For serialization compatibility - // Emulate segment calculation from previous version of this class - int sshift = 0; - int ssize = 1; - while (ssize < DEFAULT_CONCURRENCY_LEVEL) { - ++sshift; - ssize <<= 1; - } - int segmentShift = 32 - sshift; - int segmentMask = ssize - 1; - @SuppressWarnings("unchecked") Segment[] segments = (Segment[]) - new Segment[DEFAULT_CONCURRENCY_LEVEL]; - for (int i = 0; i < segments.length; ++i) { - segments[i] = new Segment(LOAD_FACTOR); - } - s.putFields().put("segments", segments); - s.putFields().put("segmentShift", segmentShift); - s.putFields().put("segmentMask", segmentMask); - s.writeFields(); - - Node[] t; - if ((t = this.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - s.writeObject(p.key); - s.writeObject(p.val); - } - } - s.writeObject(null); - s.writeObject(null); - segments = null; // throw away - } - - /** - * Reconstitutes the instance from a stream (that is, deserializes it). - * @param s the stream - * @throws ClassNotFoundException if the class of a serialized object - * could not be found - * @throws java.io.IOException if an I/O error occurs - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - /* - * To improve performance in typical cases, we create nodes - * while reading, then place in table once size is known. - * However, we must also validate uniqueness and deal with - * overpopulated bins while doing so, which requires - * specialized versions of putVal mechanics. - */ - this.sizeCtl = -1; // force exclusion for table construction - s.defaultReadObject(); - long size = 0L; - Node p = null; - for (;;) { - @SuppressWarnings("unchecked") K k = (K) s.readObject(); - @SuppressWarnings("unchecked") V v = (V) s.readObject(); - if (k != null && v != null) { - p = new Node(spread(k.hashCode()), k, v, p); - ++size; - } else { - break; - } - } - if (size == 0L) { - this.sizeCtl = 0; - } else { - int n; - if (size >= MAXIMUM_CAPACITY >>> 1) { - n = MAXIMUM_CAPACITY; - } else { - int sz = (int)size; - n = tableSizeFor(sz + (sz >>> 1) + 1); - } - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] tab = new Node[n]; - int mask = n - 1; - long added = 0L; - while (p != null) { - boolean insertAtFront; - Node next = p.next, first; - int h = p.hash, j = h & mask; - if ((first = tabAt(tab, j)) == null) { - insertAtFront = true; - } else { - K k = p.key; - if (first.hash < 0) { - TreeBin t = (TreeBin)first; - if (t.putTreeVal(h, k, p.val) == null) { - ++added; - } - insertAtFront = false; - } - else { - int binCount = 0; - insertAtFront = true; - Node q; K qk; - for (q = first; q != null; q = q.next) { - if (q.hash == h && - ((qk = q.key) == k || - qk != null && k.equals(qk))) { - insertAtFront = false; - break; - } - ++binCount; - } - if (insertAtFront && binCount >= TREEIFY_THRESHOLD) { - insertAtFront = false; - ++added; - p.next = first; - TreeNode hd = null, tl = null; - for (q = p; q != null; q = q.next) { - TreeNode t = new TreeNode - (q.hash, q.key, q.val, null, null); - if ((t.prev = tl) == null) { - hd = t; - } else { - tl.next = t; - } - tl = t; - } - setTabAt(tab, j, new TreeBin(hd)); - } - } - } - if (insertAtFront) { - ++added; - p.next = first; - setTabAt(tab, j, p); - } - p = next; - } - this.table = tab; - this.sizeCtl = n - (n >>> 2); - this.baseCount = added; - } - } - - // ConcurrentMap methods - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or {@code null} if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null - */ - @Override - public V putIfAbsent(K key, V value) { - return putVal(key, value, true); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - @Override - public boolean remove(Object key, Object value) { - if (key == null) { - throw new NullPointerException(); - } - return value != null && replaceNode(key, null, value) != null; - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if any of the arguments are null - */ - @Override - public boolean replace(K key, V oldValue, V newValue) { - if (key == null || oldValue == null || newValue == null) { - throw new NullPointerException(); - } - return replaceNode(key, newValue, oldValue) != null; - } - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or {@code null} if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null - */ - @Override - public V replace(K key, V value) { - if (key == null || value == null) { - throw new NullPointerException(); - } - return replaceNode(key, value, null); - } - - // Overrides of JDK8+ Map extension method defaults - - /** - * Returns the value to which the specified key is mapped, or the - * given default value if this map contains no mapping for the - * key. - * - * @param key the key whose associated value is to be returned - * @param defaultValue the value to return if this map contains - * no mapping for the given key - * @return the mapping for the key, if present; else the default value - * @throws NullPointerException if the specified key is null - */ - public V getOrDefault(Object key, V defaultValue) { - V v; - return (v = get(key)) == null ? defaultValue : v; - } - - public void forEach(BiAction action) { - if (action == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - action.apply(p.key, p.val); - } - } - } - - public void replaceAll(BiFun function) { - if (function == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - V oldValue = p.val; - for (K key = p.key;;) { - V newValue = function.apply(key, oldValue); - if (newValue == null) { - throw new NullPointerException(); - } - if (replaceNode(key, newValue, oldValue) != null || - (oldValue = get(key)) == null) { - break; - } - } - } - } - } - - /** - * If the specified key is not already associated with a value, - * attempts to compute its value using the given mapping function - * and enters it into this map unless {@code null}. The entire - * method invocation is performed atomically, so the function is - * applied at most once per key. Some attempted update operations - * on this map by other threads may be blocked while computation - * is in progress, so the computation should be short and simple, - * and must not attempt to update any other mappings of this map. - * - * @param key key with which the specified value is to be associated - * @param mappingFunction the function to compute a value - * @return the current (existing or computed) value associated with - * the specified key, or null if the computed value is null - * @throws NullPointerException if the specified key or mappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the mappingFunction does so, - * in which case the mapping is left unestablished - */ - public V computeIfAbsent(K key, Fun mappingFunction) { - if (key == null || mappingFunction == null) { - throw new NullPointerException(); - } - int h = spread(key.hashCode()); - V val = null; - int binCount = 0; - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) { - tab = initTable(); - } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { - Node r = new ReservationNode(); - synchronized (r) { - if (casTabAt(tab, i, null, r)) { - binCount = 1; - Node node = null; - try { - if ((val = mappingFunction.apply(key)) != null) { - node = new Node(h, key, val, null); - } - } finally { - setTabAt(tab, i, node); - } - } - } - if (binCount != 0) { - break; - } - } - else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - boolean added = false; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; V ev; - if (e.hash == h && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - val = e.val; - break; - } - Node pred = e; - if ((e = e.next) == null) { - if ((val = mappingFunction.apply(key)) != null) { - added = true; - pred.next = new Node(h, key, val, null); - } - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(h, key, null)) != null) { - val = p.val; - } else if ((val = mappingFunction.apply(key)) != null) { - added = true; - t.putTreeVal(h, key, val); - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) { - treeifyBin(tab, i); - } - if (!added) { - return val; - } - break; - } - } - } - if (val != null) { - addCount(1L, binCount); - } - return val; - } - - /** - * If the value for the specified key is present, attempts to - * compute a new mapping given the key and its current mapped - * value. The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this map. - * - * @param key key with which a value may be associated - * @param remappingFunction the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or remappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V computeIfPresent(K key, BiFun remappingFunction) { - if (key == null || remappingFunction == null) { - throw new NullPointerException(); - } - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) { - tab = initTable(); - } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { - break; - } else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - val = remappingFunction.apply(key, e.val); - if (val != null) { - e.val = val; - } else { - delta = -1; - Node en = e.next; - if (pred != null) { - pred.next = en; - } else { - setTabAt(tab, i, en); - } - } - break; - } - pred = e; - if ((e = e.next) == null) { - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(h, key, null)) != null) { - val = remappingFunction.apply(key, p.val); - if (val != null) { - p.val = val; - } else { - delta = -1; - if (t.removeTreeNode(p)) { - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - } - if (binCount != 0) { - break; - } - } - } - if (delta != 0) { - addCount(delta, binCount); - } - return val; - } - - /** - * Attempts to compute a mapping for the specified key and its - * current mapped value (or {@code null} if there is no current - * mapping). The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this Map. - * - * @param key key with which the specified value is to be associated - * @param remappingFunction the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or remappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V compute(K key, - BiFun remappingFunction) { - if (key == null || remappingFunction == null) { - throw new NullPointerException(); - } - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) { - tab = initTable(); - } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { - Node r = new ReservationNode(); - synchronized (r) { - if (casTabAt(tab, i, null, r)) { - binCount = 1; - Node node = null; - try { - if ((val = remappingFunction.apply(key, null)) != null) { - delta = 1; - node = new Node(h, key, val, null); - } - } finally { - setTabAt(tab, i, node); - } - } - } - if (binCount != 0) { - break; - } - } - else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - val = remappingFunction.apply(key, e.val); - if (val != null) { - e.val = val; - } else { - delta = -1; - Node en = e.next; - if (pred != null) { - pred.next = en; - } else { - setTabAt(tab, i, en); - } - } - break; - } - pred = e; - if ((e = e.next) == null) { - val = remappingFunction.apply(key, null); - if (val != null) { - delta = 1; - pred.next = - new Node(h, key, val, null); - } - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 1; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null) { - p = r.findTreeNode(h, key, null); - } else { - p = null; - } - V pv = p == null ? null : p.val; - val = remappingFunction.apply(key, pv); - if (val != null) { - if (p != null) { - p.val = val; - } else { - delta = 1; - t.putTreeVal(h, key, val); - } - } - else if (p != null) { - delta = -1; - if (t.removeTreeNode(p)) { - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) { - treeifyBin(tab, i); - } - break; - } - } - } - if (delta != 0) { - addCount(delta, binCount); - } - return val; - } - - /** - * If the specified key is not already associated with a - * (non-null) value, associates it with the given value. - * Otherwise, replaces the value with the results of the given - * remapping function, or removes if {@code null}. The entire - * method invocation is performed atomically. Some attempted - * update operations on this map by other threads may be blocked - * while computation is in progress, so the computation should be - * short and simple, and must not attempt to update any other - * mappings of this Map. - * - * @param key key with which the specified value is to be associated - * @param value the value to use if absent - * @param remappingFunction the function to recompute a value if present - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or the - * remappingFunction is null - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V merge(K key, V value, BiFun remappingFunction) { - if (key == null || value == null || remappingFunction == null) { - throw new NullPointerException(); - } - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = this.table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) { - tab = initTable(); - } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { - if (casTabAt(tab, i, null, new Node(h, key, value, null))) { - delta = 1; - val = value; - break; - } - } - else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - } else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - ek != null && key.equals(ek))) { - val = remappingFunction.apply(e.val, value); - if (val != null) { - e.val = val; - } else { - delta = -1; - Node en = e.next; - if (pred != null) { - pred.next = en; - } else { - setTabAt(tab, i, en); - } - } - break; - } - pred = e; - if ((e = e.next) == null) { - delta = 1; - val = value; - pred.next = - new Node(h, key, val, null); - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r = t.root; - TreeNode p = r == null ? null : - r.findTreeNode(h, key, null); - val = p == null ? value : - remappingFunction.apply(p.val, value); - if (val != null) { - if (p != null) { - p.val = val; - } else { - delta = 1; - t.putTreeVal(h, key, val); - } - } - else if (p != null) { - delta = -1; - if (t.removeTreeNode(p)) { - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) { - treeifyBin(tab, i); - } - break; - } - } - } - if (delta != 0) { - addCount(delta, binCount); - } - return val; - } - - // Hashtable legacy methods - - /** - * Legacy method testing if some key maps into the specified value - * in this table. This method is identical in functionality to - * {@link #containsValue(Object)}, 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 {@code true} if and only if some key maps to the - * {@code value} argument in this table as - * determined by the {@code equals} method; - * {@code false} otherwise - * @throws NullPointerException if the specified value is null - */ - @Deprecated public boolean contains(Object value) { - return containsValue(value); - } - - /** - * Returns an enumeration of the keys in this table. - * - * @return an enumeration of the keys in this table - * @see #keySet() - */ - public Enumeration keys() { - Node[] t; - int f = (t = this.table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, this); - } - - /** - * Returns an enumeration of the values in this table. - * - * @return an enumeration of the values in this table - * @see #values() - */ - public Enumeration elements() { - Node[] t; - int f = (t = this.table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, this); - } - - // ConcurrentHashMapV8-only methods - - /** - * Returns the number of mappings. This method should be used - * instead of {@link #size} because a ConcurrentHashMapV8 may - * contain more mappings than can be represented as an int. The - * value returned is an estimate; the actual count may differ if - * there are concurrent insertions or removals. - * - * @return the number of mappings - * @since 1.8 - */ - public long mappingCount() { - long n = sumCount(); - return n < 0L ? 0L : n; // ignore transient negative values - } - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMapV8 - * from the given type to {@code Boolean.TRUE}. - * - * @return the new set - * @since 1.8 - */ - public static KeySetView newKeySet() { - return new KeySetView - (new ConcurrentHashMapV8(), Boolean.TRUE); - } - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMapV8 - * from the given type to {@code Boolean.TRUE}. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @return the new set - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - * @since 1.8 - */ - public static KeySetView newKeySet(int initialCapacity) { - return new KeySetView - (new ConcurrentHashMapV8(initialCapacity), Boolean.TRUE); - } - - /** - * Returns a {@link Set} view of the keys in this map, using the - * given common mapped value for any additions (i.e., {@link - * Collection#add} and {@link Collection#addAll(Collection)}). - * This is of course only appropriate if it is acceptable to use - * the same value for all additions from this view. - * - * @param mappedValue the mapped value to use for any additions - * @return the set view - * @throws NullPointerException if the mappedValue is null - */ - public KeySetView keySet(V mappedValue) { - if (mappedValue == null) { - throw new NullPointerException(); - } - return new KeySetView(this, mappedValue); - } - - /* ---------------- Special Nodes -------------- */ - - /** - * A node inserted at head of bins during transfer operations. - */ - static final class ForwardingNode extends Node { - final Node[] nextTable; - ForwardingNode(Node[] tab) { - super(MOVED, null, null, null); - this.nextTable = tab; - } - - @Override - Node find(int h, Object k) { - // loop to avoid arbitrarily deep recursion on forwarding nodes - outer: for (Node[] tab = this.nextTable;;) { - Node e; int n; - if (k == null || tab == null || (n = tab.length) == 0 || - (e = tabAt(tab, n - 1 & h)) == null) { - return null; - } - for (;;) { - int eh; K ek; - if ((eh = e.hash) == h && - ((ek = e.key) == k || ek != null && k.equals(ek))) { - return e; - } - if (eh < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode)e).nextTable; - continue outer; - } else { - return e.find(h, k); - } - } - if ((e = e.next) == null) { - return null; - } - } - } - } - } - - /** - * A place-holder node used in computeIfAbsent and compute - */ - static final class ReservationNode extends Node { - ReservationNode() { - super(RESERVED, null, null, null); - } - - @Override - Node find(int h, Object k) { - return null; - } - } - - /* ---------------- Table Initialization and Resizing -------------- */ - - /** - * Initializes table, using the size recorded in sizeCtl. - */ - private final Node[] initTable() { - Node[] tab; int sc; - while ((tab = this.table) == null || tab.length == 0) { - if ((sc = this.sizeCtl) < 0) { - Thread.yield(); // lost initialization race; just spin - } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if ((tab = this.table) == null || tab.length == 0) { - int n = sc > 0 ? sc : DEFAULT_CAPACITY; - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = new Node[n]; - this.table = tab = nt; - sc = n - (n >>> 2); - } - } finally { - this.sizeCtl = sc; - } - break; - } - } - return tab; - } - - /** - * Adds to count, and if table is too small and not already - * resizing, initiates transfer. If already resizing, helps - * perform transfer if work is available. Rechecks occupancy - * after a transfer to see if another resize is already needed - * because resizings are lagging additions. - * - * @param x the count to add - * @param check if <0, don't check resize, if <= 1 only check if uncontended - */ - private final void addCount(long x, int check) { - CounterCell[] as; long b, s; - if ((as = this.counterCells) != null || - !U.compareAndSwapLong(this, BASECOUNT, b = this.baseCount, s = b + x)) { - CounterHashCode hc; CounterCell a; long v; int m; - boolean uncontended = true; - if ((hc = threadCounterHashCode.get()) == null || - as == null || (m = as.length - 1) < 0 || - (a = as[m & hc.code]) == null || - !(uncontended = - U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { - fullAddCount(x, hc, uncontended); - return; - } - if (check <= 1) { - return; - } - s = sumCount(); - } - if (check >= 0) { - Node[] tab, nt; int sc; - while (s >= (sc = this.sizeCtl) && (tab = this.table) != null && - tab.length < MAXIMUM_CAPACITY) { - if (sc < 0) { - if (sc == -1 || this.transferIndex <= this.transferOrigin || - (nt = this.nextTable) == null) { - break; - } - if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { - transfer(tab, nt); - } - } - else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) { - transfer(tab, null); - } - s = sumCount(); - } - } - } - - /** - * Helps transfer if a resize is in progress. - */ - final Node[] helpTransfer(Node[] tab, Node f) { - Node[] nextTab; int sc; - if (f instanceof ForwardingNode && - (nextTab = ((ForwardingNode)f).nextTable) != null) { - if (nextTab == this.nextTable && tab == this.table && - this.transferIndex > this.transferOrigin && (sc = this.sizeCtl) < -1 && - U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { - transfer(tab, nextTab); - } - return nextTab; - } - return this.table; - } - - /** - * Tries to presize table to accommodate the given number of elements. - * - * @param size number of elements (doesn't need to be perfectly accurate) - */ - private final void tryPresize(int size) { - int c = size >= MAXIMUM_CAPACITY >>> 1 ? MAXIMUM_CAPACITY : - tableSizeFor(size + (size >>> 1) + 1); - int sc; - while ((sc = this.sizeCtl) >= 0) { - Node[] tab = this.table; int n; - if (tab == null || (n = tab.length) == 0) { - n = sc > c ? sc : c; - if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if (this.table == tab) { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = new Node[n]; - this.table = nt; - sc = n - (n >>> 2); - } - } finally { - this.sizeCtl = sc; - } - } - } - else if (c <= sc || n >= MAXIMUM_CAPACITY) { - break; - } else if (tab == this.table && - U.compareAndSwapInt(this, SIZECTL, sc, -2)) { - transfer(tab, null); - } - } - } - - /** - * Moves and/or copies the nodes in each bin to new table. See - * above for explanation. - */ - private final void transfer(Node[] tab, Node[] nextTab) { - int n = tab.length, stride; - if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) - { - stride = MIN_TRANSFER_STRIDE; // subdivide range - } - if (nextTab == null) { // initiating - try { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = new Node[n << 1]; - nextTab = nt; - } catch (Throwable ex) { // try to cope with OOME - this.sizeCtl = Integer.MAX_VALUE; - return; - } - this.nextTable = nextTab; - this.transferOrigin = n; - this.transferIndex = n; - ForwardingNode rev = new ForwardingNode(tab); - for (int k = n; k > 0;) { // progressively reveal ready slots - int nextk = k > stride ? k - stride : 0; - for (int m = nextk; m < k; ++m) { - nextTab[m] = rev; - } - for (int m = n + nextk; m < n + k; ++m) { - nextTab[m] = rev; - } - U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); - } - } - int nextn = nextTab.length; - ForwardingNode fwd = new ForwardingNode(nextTab); - boolean advance = true; - boolean finishing = false; // to ensure sweep before committing nextTab - for (int i = 0, bound = 0;;) { - int nextIndex, nextBound, fh; Node f; - while (advance) { - if (--i >= bound || finishing) { - advance = false; - } else if ((nextIndex = this.transferIndex) <= this.transferOrigin) { - i = -1; - advance = false; - } - else if (U.compareAndSwapInt - (this, TRANSFERINDEX, nextIndex, - nextBound = nextIndex > stride ? - nextIndex - stride : 0)) { - bound = nextBound; - i = nextIndex - 1; - advance = false; - } - } - if (i < 0 || i >= n || i + n >= nextn) { - if (finishing) { - this.nextTable = null; - this.table = nextTab; - this.sizeCtl = (n << 1) - (n >>> 1); - return; - } - for (int sc;;) { - if (U.compareAndSwapInt(this, SIZECTL, sc = this.sizeCtl, ++sc)) { - if (sc != -1) { - return; - } - finishing = advance = true; - i = n; // recheck before commit - break; - } - } - } - else if ((f = tabAt(tab, i)) == null) { - if (casTabAt(tab, i, null, fwd)) { - setTabAt(nextTab, i, null); - setTabAt(nextTab, i + n, null); - advance = true; - } - } - else if ((fh = f.hash) == MOVED) { - advance = true; // already processed - } else { - synchronized (f) { - if (tabAt(tab, i) == f) { - Node ln, hn; - if (fh >= 0) { - int runBit = fh & n; - Node lastRun = f; - for (Node p = f.next; p != null; p = p.next) { - int b = p.hash & n; - if (b != runBit) { - runBit = b; - lastRun = p; - } - } - if (runBit == 0) { - ln = lastRun; - hn = null; - } - else { - hn = lastRun; - ln = null; - } - for (Node p = f; p != lastRun; p = p.next) { - int ph = p.hash; K pk = p.key; V pv = p.val; - if ((ph & n) == 0) { - ln = new Node(ph, pk, pv, ln); - } else { - hn = new Node(ph, pk, pv, hn); - } - } - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } - else if (f instanceof TreeBin) { - TreeBin t = (TreeBin)f; - TreeNode lo = null, loTail = null; - TreeNode hi = null, hiTail = null; - int lc = 0, hc = 0; - for (Node e = t.first; e != null; e = e.next) { - int h = e.hash; - TreeNode p = new TreeNode - (h, e.key, e.val, null, null); - if ((h & n) == 0) { - if ((p.prev = loTail) == null) { - lo = p; - } else { - loTail.next = p; - } - loTail = p; - ++lc; - } - else { - if ((p.prev = hiTail) == null) { - hi = p; - } else { - hiTail.next = p; - } - hiTail = p; - ++hc; - } - } - ln = lc <= UNTREEIFY_THRESHOLD ? untreeify(lo) : - hc != 0 ? new TreeBin(lo) : t; - hn = hc <= UNTREEIFY_THRESHOLD ? untreeify(hi) : - lc != 0 ? new TreeBin(hi) : t; - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } - } - } - } - } - } - - /* ---------------- Conversion from/to TreeBins -------------- */ - - /** - * Replaces all linked nodes in bin at given index unless table is - * too small, in which case resizes instead. - */ - private final void treeifyBin(Node[] tab, int index) { - Node b; int n, sc; - if (tab != null) { - if ((n = tab.length) < MIN_TREEIFY_CAPACITY) { - if (tab == this.table && (sc = this.sizeCtl) >= 0 && - U.compareAndSwapInt(this, SIZECTL, sc, -2)) { - transfer(tab, null); - } - } - else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { - synchronized (b) { - if (tabAt(tab, index) == b) { - TreeNode hd = null, tl = null; - for (Node e = b; e != null; e = e.next) { - TreeNode p = - new TreeNode(e.hash, e.key, e.val, - null, null); - if ((p.prev = tl) == null) { - hd = p; - } else { - tl.next = p; - } - tl = p; - } - setTabAt(tab, index, new TreeBin(hd)); - } - } - } - } - } - - /** - * Returns a list on non-TreeNodes replacing those in given list. - */ - static Node untreeify(Node b) { - Node hd = null, tl = null; - for (Node q = b; q != null; q = q.next) { - Node p = new Node(q.hash, q.key, q.val, null); - if (tl == null) { - hd = p; - } else { - tl.next = p; - } - tl = p; - } - return hd; - } - - /* ---------------- TreeNodes -------------- */ - - /** - * Nodes for use in TreeBins - */ - static final class TreeNode extends Node { - TreeNode parent; // red-black tree links - TreeNode left; - TreeNode right; - TreeNode prev; // needed to unlink next upon deletion - boolean red; - - TreeNode(int hash, K key, V val, Node next, - TreeNode parent) { - super(hash, key, val, next); - this.parent = parent; - } - - @Override - Node find(int h, Object k) { - return findTreeNode(h, k, null); - } - - /** - * Returns the TreeNode (or null if not found) for the given key - * starting at given root. - */ - final TreeNode findTreeNode(int h, Object k, Class kc) { - if (k != null) { - TreeNode p = this; - do { - int ph, dir; K pk; TreeNode q; - TreeNode pl = p.left, pr = p.right; - if ((ph = p.hash) > h) { - p = pl; - } else if (ph < h) { - p = pr; - } else if ((pk = p.key) == k || pk != null && k.equals(pk)) { - return p; - } else if (pl == null) { - p = pr; - } else if (pr == null) { - p = pl; - } else if ((kc != null || - (kc = comparableClassFor(k)) != null) && - (dir = compareComparables(kc, k, pk)) != 0) { - p = dir < 0 ? pl : pr; - } else if ((q = pr.findTreeNode(h, k, kc)) != null) { - return q; - } else { - p = pl; - } - } while (p != null); - } - return null; - } - } - - /* ---------------- TreeBins -------------- */ - - /** - * TreeNodes used at the heads of bins. TreeBins do not hold user - * keys or values, but instead point to list of TreeNodes and - * their root. They also maintain a parasitic read-write lock - * forcing writers (who hold bin lock) to wait for readers (who do - * not) to complete before tree restructuring operations. - */ - static final class TreeBin extends Node { - TreeNode root; - volatile TreeNode first; - volatile Thread waiter; - volatile int lockState; - // values for lockState - static final int WRITER = 1; // set while holding write lock - static final int WAITER = 2; // set when waiting for write lock - static final int READER = 4; // increment value for setting read lock - - /** - * Tie-breaking utility for ordering insertions when equal - * hashCodes and non-comparable. We don't require a total - * order, just a consistent insertion rule to maintain - * equivalence across rebalancings. Tie-breaking further than - * necessary simplifies testing a bit. - */ - static int tieBreakOrder(Object a, Object b) { - int d; - if (a == null || b == null || - (d = a.getClass().getName(). - compareTo(b.getClass().getName())) == 0) { - d = System.identityHashCode(a) <= System.identityHashCode(b) ? - -1 : 1; - } - return d; - } - - /** - * Creates bin with initial set of nodes headed by b. - */ - TreeBin(TreeNode b) { - super(TREEBIN, null, null, null); - this.first = b; - TreeNode r = null; - for (TreeNode x = b, next; x != null; x = next) { - next = (TreeNode)x.next; - x.left = x.right = null; - if (r == null) { - x.parent = null; - x.red = false; - r = x; - } - else { - K k = x.key; - int h = x.hash; - Class kc = null; - for (TreeNode p = r;;) { - int dir, ph; - K pk = p.key; - if ((ph = p.hash) > h) { - dir = -1; - } else if (ph < h) { - dir = 1; - } else if (kc == null && - (kc = comparableClassFor(k)) == null || - (dir = compareComparables(kc, k, pk)) == 0) { - dir = tieBreakOrder(k, pk); - } - TreeNode xp = p; - if ((p = dir <= 0 ? p.left : p.right) == null) { - x.parent = xp; - if (dir <= 0) { - xp.left = x; - } else { - xp.right = x; - } - r = balanceInsertion(r, x); - break; - } - } - } - } - this.root = r; - assert checkInvariants(this.root); - } - - /** - * Acquires write lock for tree restructuring. - */ - private final void lockRoot() { - if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) - { - contendedLock(); // offload to separate method - } - } - - /** - * Releases write lock for tree restructuring. - */ - private final void unlockRoot() { - this.lockState = 0; - } - - /** - * Possibly blocks awaiting root lock. - */ - private final void contendedLock() { - boolean waiting = false; - for (int s;;) { - if (((s = this.lockState) & WRITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { - if (waiting) { - this.waiter = null; - } - return; - } - } - else if ((s | WAITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { - waiting = true; - this.waiter = Thread.currentThread(); - } - } - else if (waiting) { - LockSupport.park(this); - } - } - } - - /** - * Returns matching node or null if none. Tries to search - * using tree comparisons from root, but continues linear - * search when lock not available. - */ -@Override -final Node find(int h, Object k) { - if (k != null) { - for (Node e = this.first; e != null; e = e.next) { - int s; K ek; - if (((s = this.lockState) & (WAITER|WRITER)) != 0) { - if (e.hash == h && - ((ek = e.key) == k || ek != null && k.equals(ek))) { - return e; - } - } - else if (U.compareAndSwapInt(this, LOCKSTATE, s, - s + READER)) { - TreeNode r, p; - try { - p = (r = this.root) == null ? null : - r.findTreeNode(h, k, null); - } finally { - Thread w; - int ls; - do {} while (!U.compareAndSwapInt - (this, LOCKSTATE, - ls = this.lockState, ls - READER)); - if (ls == (READER|WAITER) && (w = this.waiter) != null) { - LockSupport.unpark(w); - } - } - return p; - } - } - } - return null; - } - - /** - * Finds or adds a node. - * @return null if added - */ - final TreeNode putTreeVal(int h, K k, V v) { - Class kc = null; - boolean searched = false; - for (TreeNode p = this.root;;) { - int dir, ph; K pk; - if (p == null) { - this.first = this.root = new TreeNode(h, k, v, null, null); - break; - } - else if ((ph = p.hash) > h) { - dir = -1; - } else if (ph < h) { - dir = 1; - } else if ((pk = p.key) == k || pk != null && k.equals(pk)) { - return p; - } else if (kc == null && - (kc = comparableClassFor(k)) == null || - (dir = compareComparables(kc, k, pk)) == 0) { - if (!searched) { - TreeNode q, ch; - searched = true; - if ((ch = p.left) != null && - (q = ch.findTreeNode(h, k, kc)) != null || - (ch = p.right) != null && - (q = ch.findTreeNode(h, k, kc)) != null) { - return q; - } - } - dir = tieBreakOrder(k, pk); - } - - TreeNode xp = p; - if ((p = dir <= 0 ? p.left : p.right) == null) { - TreeNode x, f = this.first; - this.first = x = new TreeNode(h, k, v, f, xp); - if (f != null) { - f.prev = x; - } - if (dir <= 0) { - xp.left = x; - } else { - xp.right = x; - } - if (!xp.red) { - x.red = true; - } else { - lockRoot(); - try { - this.root = balanceInsertion(this.root, x); - } finally { - unlockRoot(); - } - } - break; - } - } - assert checkInvariants(this.root); - return null; - } - - /** - * Removes the given node, that must be present before this - * call. This is messier than typical red-black deletion code - * because we cannot swap the contents of an interior node - * with a leaf successor that is pinned by "next" pointers - * that are accessible independently of lock. So instead we - * swap the tree linkages. - * - * @return true if now too small, so should be untreeified - */ - final boolean removeTreeNode(TreeNode p) { - TreeNode next = (TreeNode)p.next; - TreeNode pred = p.prev; // unlink traversal pointers - TreeNode r, rl; - if (pred == null) { - this.first = next; - } else { - pred.next = next; - } - if (next != null) { - next.prev = pred; - } - if (this.first == null) { - this.root = null; - return true; - } - if ((r = this.root) == null || r.right == null || // too small - (rl = r.left) == null || rl.left == null) { - return true; - } - lockRoot(); - try { - TreeNode replacement; - TreeNode pl = p.left; - TreeNode pr = p.right; - if (pl != null && pr != null) { - TreeNode s = pr, sl; - while ((sl = s.left) != null) { - s = sl; - } - boolean c = s.red; s.red = p.red; p.red = c; // swap colors - TreeNode sr = s.right; - TreeNode pp = p.parent; - if (s == pr) { // p was s's direct parent - p.parent = s; - s.right = p; - } - else { - TreeNode sp = s.parent; - if ((p.parent = sp) != null) { - if (s == sp.left) { - sp.left = p; - } else { - sp.right = p; - } - } - if ((s.right = pr) != null) { - pr.parent = s; - } - } - p.left = null; - if ((p.right = sr) != null) { - sr.parent = p; - } - if ((s.left = pl) != null) { - pl.parent = s; - } - if ((s.parent = pp) == null) { - r = s; - } else if (p == pp.left) { - pp.left = s; - } else { - pp.right = s; - } - if (sr != null) { - replacement = sr; - } else { - replacement = p; - } - } - else if (pl != null) { - replacement = pl; - } else if (pr != null) { - replacement = pr; - } else { - replacement = p; - } - if (replacement != p) { - TreeNode pp = replacement.parent = p.parent; - if (pp == null) { - r = replacement; - } else if (p == pp.left) { - pp.left = replacement; - } else { - pp.right = replacement; - } - p.left = p.right = p.parent = null; - } - - this.root = p.red ? r : balanceDeletion(r, replacement); - - if (p == replacement) { // detach pointers - TreeNode pp; - if ((pp = p.parent) != null) { - if (p == pp.left) { - pp.left = null; - } else if (p == pp.right) { - pp.right = null; - } - p.parent = null; - } - } - } finally { - unlockRoot(); - } - assert checkInvariants(this.root); - return false; - } - - /* ------------------------------------------------------------ */ - // Red-black tree methods, all adapted from CLR - - static TreeNode rotateLeft(TreeNode root, - TreeNode p) { - TreeNode r, pp, rl; - if (p != null && (r = p.right) != null) { - if ((rl = p.right = r.left) != null) { - rl.parent = p; - } - if ((pp = r.parent = p.parent) == null) { - (root = r).red = false; - } else if (pp.left == p) { - pp.left = r; - } else { - pp.right = r; - } - r.left = p; - p.parent = r; - } - return root; - } - - static TreeNode rotateRight(TreeNode root, - TreeNode p) { - TreeNode l, pp, lr; - if (p != null && (l = p.left) != null) { - if ((lr = p.left = l.right) != null) { - lr.parent = p; - } - if ((pp = l.parent = p.parent) == null) { - (root = l).red = false; - } else if (pp.right == p) { - pp.right = l; - } else { - pp.left = l; - } - l.right = p; - p.parent = l; - } - return root; - } - - static TreeNode balanceInsertion(TreeNode root, - TreeNode x) { - x.red = true; - for (TreeNode xp, xpp, xppl, xppr;;) { - if ((xp = x.parent) == null) { - x.red = false; - return x; - } - else if (!xp.red || (xpp = xp.parent) == null) { - return root; - } - if (xp == (xppl = xpp.left)) { - if ((xppr = xpp.right) != null && xppr.red) { - xppr.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } - else { - if (x == xp.right) { - root = rotateLeft(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateRight(root, xpp); - } - } - } - } - else { - if (xppl != null && xppl.red) { - xppl.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } - else { - if (x == xp.left) { - root = rotateRight(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateLeft(root, xpp); - } - } - } - } - } - } - - static TreeNode balanceDeletion(TreeNode root, - TreeNode x) { - for (TreeNode xp, xpl, xpr;;) { - if (x == null || x == root) { - return root; - } else if ((xp = x.parent) == null) { - x.red = false; - return x; - } - else if (x.red) { - x.red = false; - return root; - } - else if ((xpl = xp.left) == x) { - if ((xpr = xp.right) != null && xpr.red) { - xpr.red = false; - xp.red = true; - root = rotateLeft(root, xp); - xpr = (xp = x.parent) == null ? null : xp.right; - } - if (xpr == null) { - x = xp; - } else { - TreeNode sl = xpr.left, sr = xpr.right; - if ((sr == null || !sr.red) && - (sl == null || !sl.red)) { - xpr.red = true; - x = xp; - } - else { - if (sr == null || !sr.red) { - if (sl != null) { - sl.red = false; - } - xpr.red = true; - root = rotateRight(root, xpr); - xpr = (xp = x.parent) == null ? - null : xp.right; - } - if (xpr != null) { - xpr.red = xp == null ? false : xp.red; - if ((sr = xpr.right) != null) { - sr.red = false; - } - } - if (xp != null) { - xp.red = false; - root = rotateLeft(root, xp); - } - x = root; - } - } - } - else { // symmetric - if (xpl != null && xpl.red) { - xpl.red = false; - xp.red = true; - root = rotateRight(root, xp); - xpl = (xp = x.parent) == null ? null : xp.left; - } - if (xpl == null) { - x = xp; - } else { - TreeNode sl = xpl.left, sr = xpl.right; - if ((sl == null || !sl.red) && - (sr == null || !sr.red)) { - xpl.red = true; - x = xp; - } - else { - if (sl == null || !sl.red) { - if (sr != null) { - sr.red = false; - } - xpl.red = true; - root = rotateLeft(root, xpl); - xpl = (xp = x.parent) == null ? - null : xp.left; - } - if (xpl != null) { - xpl.red = xp == null ? false : xp.red; - if ((sl = xpl.left) != null) { - sl.red = false; - } - } - if (xp != null) { - xp.red = false; - root = rotateRight(root, xp); - } - x = root; - } - } - } - } - } - - /** - * Recursive invariant check - */ - static boolean checkInvariants(TreeNode t) { - TreeNode tp = t.parent, tl = t.left, tr = t.right, - tb = t.prev, tn = (TreeNode)t.next; - if (tb != null && tb.next != t) { - return false; - } - if (tn != null && tn.prev != t) { - return false; - } - if (tp != null && t != tp.left && t != tp.right) { - return false; - } - if (tl != null && (tl.parent != t || tl.hash > t.hash)) { - return false; - } - if (tr != null && (tr.parent != t || tr.hash < t.hash)) { - return false; - } - if (t.red && tl != null && tl.red && tr != null && tr.red) { - return false; - } - if (tl != null && !checkInvariants(tl)) { - return false; - } - if (tr != null && !checkInvariants(tr)) { - return false; - } - return true; - } - - private static final sun.misc.Unsafe U; - private static final long LOCKSTATE; - static { - try { - U = getUnsafe(); - Class k = TreeBin.class; - LOCKSTATE = U.objectFieldOffset - (k.getDeclaredField("lockState")); - } catch (Exception e) { - throw new Error(e); - } - } - } - - /* ----------------Table Traversal -------------- */ - - /** - * Encapsulates traversal for methods such as containsValue; also - * serves as a base class for other iterators and spliterators. - * - * Method advance visits once each still-valid node that was - * reachable upon iterator construction. It might miss some that - * were added to a bin after the bin was visited, which is OK wrt - * consistency guarantees. Maintaining this property in the face - * of possible ongoing resizes requires a fair amount of - * bookkeeping state that is difficult to optimize away amidst - * volatile accesses. Even so, traversal maintains reasonable - * throughput. - * - * Normally, iteration proceeds bin-by-bin traversing lists. - * However, if the table has been resized, then all future steps - * must traverse both the bin at the current index as well as at - * (index + baseSize); and so on for further resizings. To - * paranoically cope with potential sharing by users of iterators - * across threads, iteration terminates if a bounds checks fails - * for a table read. - */ - static class Traverser { - Node[] tab; // current table; updated if resized - Node next; // the next entry to use - int index; // index of bin to use next - int baseIndex; // current index of initial table - int baseLimit; // index bound for initial table - final int baseSize; // initial table size - - Traverser(Node[] tab, int size, int index, int limit) { - this.tab = tab; - this.baseSize = size; - this.baseIndex = this.index = index; - this.baseLimit = limit; - this.next = null; - } - - /** - * Advances if possible, returning next valid node, or null if none. - */ - final Node advance() { - Node e; - if ((e = this.next) != null) { - e = e.next; - } - for (;;) { - Node[] t; int i, n; K ek; // must use locals in checks - if (e != null) { - return this.next = e; - } - if (this.baseIndex >= this.baseLimit || (t = this.tab) == null || - (n = t.length) <= (i = this.index) || i < 0) { - return this.next = null; - } - if ((e = tabAt(t, this.index)) != null && e.hash < 0) { - if (e instanceof ForwardingNode) { - this.tab = ((ForwardingNode)e).nextTable; - e = null; - continue; - } - else if (e instanceof TreeBin) { - e = ((TreeBin)e).first; - } else { - e = null; - } - } - if ((this.index += this.baseSize) >= n) - { - this.index = ++this.baseIndex; // visit upper slots if present - } - } - } - } - - /** - * Base of key, value, and entry Iterators. Adds fields to - * Traverser to support iterator.remove. - */ - static class BaseIterator extends Traverser { - final ConcurrentHashMapV8 map; - Node lastReturned; - BaseIterator(Node[] tab, int size, int index, int limit, - ConcurrentHashMapV8 map) { - super(tab, size, index, limit); - this.map = map; - advance(); - } - - public final boolean hasNext() { return this.next != null; } - public final boolean hasMoreElements() { return this.next != null; } - - public final void remove() { - Node p; - if ((p = this.lastReturned) == null) { - throw new IllegalStateException(); - } - this.lastReturned = null; - this.map.replaceNode(p.key, null, null); - } - } - - static final class KeyIterator extends BaseIterator - implements Iterator, Enumeration { - KeyIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - @Override - public final K next() { - Node p; - if ((p = this.next) == null) { - throw new NoSuchElementException(); - } - K k = p.key; - this.lastReturned = p; - advance(); - return k; - } - - @Override - public final K nextElement() { return next(); } - } - - static final class ValueIterator extends BaseIterator - implements Iterator, Enumeration { - ValueIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - @Override - public final V next() { - Node p; - if ((p = this.next) == null) { - throw new NoSuchElementException(); - } - V v = p.val; - this.lastReturned = p; - advance(); - return v; - } - - @Override - public final V nextElement() { return next(); } - } - - static final class EntryIterator extends BaseIterator - implements Iterator> { - EntryIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - @Override - public final Map.Entry next() { - Node p; - if ((p = this.next) == null) { - throw new NoSuchElementException(); - } - K k = p.key; - V v = p.val; - this.lastReturned = p; - advance(); - return new MapEntry(k, v, this.map); - } - } - - /** - * Exported Entry for EntryIterator - */ - static final class MapEntry implements Map.Entry { - final K key; // non-null - V val; // non-null - final ConcurrentHashMapV8 map; - MapEntry(K key, V val, ConcurrentHashMapV8 map) { - this.key = key; - this.val = val; - this.map = map; - } - @Override - public K getKey() { return this.key; } - @Override - public V getValue() { return this.val; } - @Override - public int hashCode() { return this.key.hashCode() ^ this.val.hashCode(); } - @Override - public String toString() { return this.key + "=" + this.val; } - - @Override - public boolean equals(Object o) { - Object k, v; Map.Entry e; - return o instanceof Map.Entry && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - (k == this.key || k.equals(this.key)) && - (v == this.val || v.equals(this.val)); - } - - /** - * Sets our entry's value and writes through to the map. The - * value to return is somewhat arbitrary here. Since we do not - * necessarily track asynchronous changes, the most recent - * "previous" value could be different from what we return (or - * could even have been removed, in which case the put will - * re-establish). We do not and cannot guarantee more. - */ - @Override - public V setValue(V value) { - if (value == null) { - throw new NullPointerException(); - } - V v = this.val; - this.val = value; - this.map.put(this.key, value); - return v; - } - } - - static final class KeySpliterator extends Traverser - implements ConcurrentHashMapSpliterator { - long est; // size estimate - KeySpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - @Override - public ConcurrentHashMapSpliterator trySplit() { - int i, f, h; - return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : - new KeySpliterator(this.tab, this.baseSize, this.baseLimit = h, - f, this.est >>>= 1); - } - - @Override - public void forEachRemaining(Action action) { - if (action == null) { - throw new NullPointerException(); - } - for (Node p; (p = advance()) != null;) { - action.apply(p.key); - } - } - - @Override - public boolean tryAdvance(Action action) { - if (action == null) { - throw new NullPointerException(); - } - Node p; - if ((p = advance()) == null) { - return false; - } - action.apply(p.key); - return true; - } - - @Override - public long estimateSize() { return this.est; } - - } - - static final class ValueSpliterator extends Traverser - implements ConcurrentHashMapSpliterator { - long est; // size estimate - ValueSpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - @Override - public ConcurrentHashMapSpliterator trySplit() { - int i, f, h; - return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : - new ValueSpliterator(this.tab, this.baseSize, this.baseLimit = h, - f, this.est >>>= 1); - } - - @Override - public void forEachRemaining(Action action) { - if (action == null) { - throw new NullPointerException(); - } - for (Node p; (p = advance()) != null;) { - action.apply(p.val); - } - } - - @Override - public boolean tryAdvance(Action action) { - if (action == null) { - throw new NullPointerException(); - } - Node p; - if ((p = advance()) == null) { - return false; - } - action.apply(p.val); - return true; - } - - @Override - public long estimateSize() { return this.est; } - - } - - static final class EntrySpliterator extends Traverser - implements ConcurrentHashMapSpliterator> { - final ConcurrentHashMapV8 map; // To export MapEntry - long est; // size estimate - EntrySpliterator(Node[] tab, int size, int index, int limit, - long est, ConcurrentHashMapV8 map) { - super(tab, size, index, limit); - this.map = map; - this.est = est; - } - - @Override - public ConcurrentHashMapSpliterator> trySplit() { - int i, f, h; - return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : - new EntrySpliterator(this.tab, this.baseSize, this.baseLimit = h, - f, this.est >>>= 1, this.map); - } - - @Override - public void forEachRemaining(Action> action) { - if (action == null) { - throw new NullPointerException(); - } - for (Node p; (p = advance()) != null; ) { - action.apply(new MapEntry(p.key, p.val, this.map)); - } - } - - @Override - public boolean tryAdvance(Action> action) { - if (action == null) { - throw new NullPointerException(); - } - Node p; - if ((p = advance()) == null) { - return false; - } - action.apply(new MapEntry(p.key, p.val, this.map)); - return true; - } - - @Override - public long estimateSize() { return this.est; } - - } - - /* ----------------Views -------------- */ - - /** - * Base class for views. - */ - abstract static class CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - final ConcurrentHashMapV8 map; - CollectionView(ConcurrentHashMapV8 map) { this.map = map; } - - /** - * Returns the map backing this view. - * - * @return the map backing this view - */ - public ConcurrentHashMapV8 getMap() { return this.map; } - - /** - * Removes all of the elements from this view, by removing all - * the mappings from the map backing this view. - */ - @Override - public final void clear() { this.map.clear(); } - @Override - public final int size() { return this.map.size(); } - @Override - public final boolean isEmpty() { return this.map.isEmpty(); } - - // implementations below rely on concrete classes supplying these - // abstract methods - /** - * Returns a "weakly consistent" iterator that will never - * throw {@link ConcurrentModificationException}, and - * guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not - * guaranteed to) reflect any modifications subsequent to - * construction. - */ - @Override - public abstract Iterator iterator(); - @Override - public abstract boolean contains(Object o); - @Override - public abstract boolean remove(Object o); - - private static final String oomeMsg = "Required array size too large"; - - @Override - public final Object[] toArray() { - long sz = this.map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) { - throw new OutOfMemoryError(oomeMsg); - } - int n = (int)sz; - Object[] r = new Object[n]; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) { - throw new OutOfMemoryError(oomeMsg); - } - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { - n = MAX_ARRAY_SIZE; - } else { - n += (n >>> 1) + 1; - } - r = Arrays.copyOf(r, n); - } - r[i++] = e; - } - return i == n ? r : Arrays.copyOf(r, i); - } - - @Override - @SuppressWarnings("unchecked") - public final T[] toArray(T[] a) { - long sz = this.map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) { - throw new OutOfMemoryError(oomeMsg); - } - int m = (int)sz; - T[] r = a.length >= m ? a : - (T[])java.lang.reflect.Array - .newInstance(a.getClass().getComponentType(), m); - int n = r.length; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) { - throw new OutOfMemoryError(oomeMsg); - } - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { - n = MAX_ARRAY_SIZE; - } else { - n += (n >>> 1) + 1; - } - r = Arrays.copyOf(r, n); - } - r[i++] = (T)e; - } - if (a == r && i < n) { - r[i] = null; // null-terminate - return r; - } - return i == n ? r : Arrays.copyOf(r, i); - } - - /** - * Returns a string representation of this collection. - * The string representation consists of the string representations - * of the collection's elements in the order they are returned by - * its iterator, enclosed in square brackets ({@code "[]"}). - * Adjacent elements are separated by the characters {@code ", "} - * (comma and space). Elements are converted to strings as by - * {@link String#valueOf(Object)}. - * - * @return a string representation of this collection - */ - @Override - public final String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('['); - Iterator it = iterator(); - if (it.hasNext()) { - for (;;) { - Object e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) { - break; - } - sb.append(',').append(' '); - } - } - return sb.append(']').toString(); - } - - @Override - public final boolean containsAll(Collection c) { - if (c != this) { - for (Object e : c) { - if (e == null || !contains(e)) { - return false; - } - } - } - return true; - } - - @Override - public final boolean removeAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - @Override - public final boolean retainAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (!c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in - * which additions may optionally be enabled by mapping to a - * common value. This class cannot be directly instantiated. - * See {@link #keySet() keySet()}, - * {@link #keySet(Object) keySet(V)}, - * {@link #newKeySet() newKeySet()}, - * {@link #newKeySet(int) newKeySet(int)}. - * - * @since 1.8 - */ - public static class KeySetView extends CollectionView - implements Set, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - private final V value; - KeySetView(ConcurrentHashMapV8 map, V value) { // non-public - super(map); - this.value = value; - } - - /** - * Returns the default mapped value for additions, - * or {@code null} if additions are not supported. - * - * @return the default mapped value for additions, or {@code null} - * if not supported - */ - public V getMappedValue() { return this.value; } - - /** - * {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - @Override - public boolean contains(Object o) { return this.map.containsKey(o); } - - /** - * Removes the key from this map view, by removing the key (and its - * corresponding value) from the backing map. This method does - * nothing if the key is not in the map. - * - * @param o the key to be removed from the backing map - * @return {@code true} if the backing map contained the specified key - * @throws NullPointerException if the specified key is null - */ - @Override - public boolean remove(Object o) { return this.map.remove(o) != null; } - - /** - * @return an iterator over the keys of the backing map - */ - @Override - public Iterator iterator() { - Node[] t; - ConcurrentHashMapV8 m = this.map; - int f = (t = m.table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, m); - } - - /** - * Adds the specified key to this set view by mapping the key to - * the default mapped value in the backing map, if defined. - * - * @param e key to be added - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the specified key is null - * @throws UnsupportedOperationException if no default mapped value - * for additions was provided - */ - @Override - public boolean add(K e) { - V v; - if ((v = this.value) == null) { - throw new UnsupportedOperationException(); - } - return this.map.putVal(e, v, true) == null; - } - - /** - * Adds all of the elements in the specified collection to this set, - * as if by calling {@link #add} on each one. - * - * @param c the elements to be inserted into this set - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the collection or any of its - * elements are {@code null} - * @throws UnsupportedOperationException if no default mapped value - * for additions was provided - */ - @Override - public boolean addAll(Collection c) { - boolean added = false; - V v; - if ((v = this.value) == null) { - throw new UnsupportedOperationException(); - } - for (K e : c) { - if (this.map.putVal(e, v, true) == null) { - added = true; - } - } - return added; - } - - @Override - public int hashCode() { - int h = 0; - for (K e : this) { - h += e.hashCode(); - } - return h; - } - - @Override - public boolean equals(Object o) { - Set c; - return o instanceof Set && - ((c = (Set)o) == this || - containsAll(c) && c.containsAll(this)); - } - - public ConcurrentHashMapSpliterator spliterator() { - Node[] t; - ConcurrentHashMapV8 m = this.map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new KeySpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Action action) { - if (action == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - action.apply(p.key); - } - } - } - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Collection} of - * values, in which additions are disabled. This class cannot be - * directly instantiated. See {@link #values()}. - */ - static final class ValuesView extends CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - ValuesView(ConcurrentHashMapV8 map) { super(map); } - @Override - public final boolean contains(Object o) { - return this.map.containsValue(o); - } - - @Override - public final boolean remove(Object o) { - if (o != null) { - for (Iterator it = iterator(); it.hasNext();) { - if (o.equals(it.next())) { - it.remove(); - return true; - } - } - } - return false; - } - - @Override - public final Iterator iterator() { - ConcurrentHashMapV8 m = this.map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, m); - } - - @Override - public final boolean add(V e) { - throw new UnsupportedOperationException(); - } - @Override - public final boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - public ConcurrentHashMapSpliterator spliterator() { - Node[] t; - ConcurrentHashMapV8 m = this.map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new ValueSpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Action action) { - if (action == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - action.apply(p.val); - } - } - } - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) - * entries. This class cannot be directly instantiated. See - * {@link #entrySet()}. - */ - static final class EntrySetView extends CollectionView> - implements Set>, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - EntrySetView(ConcurrentHashMapV8 map) { super(map); } - - @Override - public boolean contains(Object o) { - Object k, v, r; Map.Entry e; - return o instanceof Map.Entry && - (k = (e = (Map.Entry)o).getKey()) != null && - (r = this.map.get(k)) != null && - (v = e.getValue()) != null && - (v == r || v.equals(r)); - } - - @Override - public boolean remove(Object o) { - Object k, v; Map.Entry e; - return o instanceof Map.Entry && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - this.map.remove(k, v); - } - - /** - * @return an iterator over the entries of the backing map - */ - @Override - public Iterator> iterator() { - ConcurrentHashMapV8 m = this.map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new EntryIterator(t, f, 0, f, m); - } - - @Override - public boolean add(Entry e) { - return this.map.putVal(e.getKey(), e.getValue(), false) == null; - } - - @Override - public boolean addAll(Collection> c) { - boolean added = false; - for (Entry e : c) { - if (add(e)) { - added = true; - } - } - return added; - } - - @Override - public final int hashCode() { - int h = 0; - Node[] t; - if ((t = this.map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - h += p.hashCode(); - } - } - return h; - } - - @Override - public final boolean equals(Object o) { - Set c; - return o instanceof Set && - ((c = (Set)o) == this || - containsAll(c) && c.containsAll(this)); - } - - public ConcurrentHashMapSpliterator> spliterator() { - Node[] t; - ConcurrentHashMapV8 m = this.map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new EntrySpliterator(t, f, 0, f, n < 0L ? 0L : n, m); - } - - public void forEach(Action> action) { - if (action == null) { - throw new NullPointerException(); - } - Node[] t; - if ((t = this.map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - action.apply(new MapEntry(p.key, p.val, this.map)); - } - } - } - - } - - /* ---------------- Counters -------------- */ - - // Adapted from LongAdder and Striped64. - // See their internal docs for explanation. - - // A padded cell for distributing counts - static final class CounterCell { - volatile long p0, p1, p2, p3, p4, p5, p6; - volatile long value; - volatile long q0, q1, q2, q3, q4, q5, q6; - CounterCell(long x) { this.value = x; } - } - - /** - * Holder for the thread-local hash code determining which - * CounterCell to use. The code is initialized via the - * counterHashCodeGenerator, but may be moved upon collisions. - */ - static final class CounterHashCode { - int code; - } - - /** - * Generates initial value for per-thread CounterHashCodes. - */ - static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); - - /** - * Increment for counterHashCodeGenerator. See class ThreadLocal - * for explanation. - */ - static final int SEED_INCREMENT = 0x61c88647; - - /** - * Per-thread counter hash codes. Shared across all instances. - */ - static final ThreadLocal threadCounterHashCode = - new ThreadLocal(); - - - final long sumCount() { - CounterCell[] as = this.counterCells; CounterCell a; - long sum = this.baseCount; - if (as != null) { - for (int i = 0; i < as.length; ++i) { - if ((a = as[i]) != null) { - sum += a.value; - } - } - } - return sum; - } - - // See LongAdder version for explanation - private final void fullAddCount(long x, CounterHashCode hc, - boolean wasUncontended) { - int h; - if (hc == null) { - hc = new CounterHashCode(); - int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); - h = hc.code = s == 0 ? 1 : s; // Avoid zero - threadCounterHashCode.set(hc); - } else { - h = hc.code; - } - boolean collide = false; // True if last slot nonempty - for (;;) { - CounterCell[] as; CounterCell a; int n; long v; - if ((as = this.counterCells) != null && (n = as.length) > 0) { - if ((a = as[n - 1 & h]) == null) { - if (this.cellsBusy == 0) { // Try to attach new Cell - CounterCell r = new CounterCell(x); // Optimistic create - if (this.cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean created = false; - try { // Recheck under lock - CounterCell[] rs; int m, j; - if ((rs = this.counterCells) != null && - (m = rs.length) > 0 && - rs[j = m - 1 & h] == null) { - rs[j] = r; - created = true; - } - } finally { - this.cellsBusy = 0; - } - if (created) { - break; - } - continue; // Slot is now non-empty - } - } - collide = false; - } - else if (!wasUncontended) { - wasUncontended = true; // Continue after rehash - } else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) { - break; - } else if (this.counterCells != as || n >= NCPU) { - collide = false; // At max size or stale - } else if (!collide) { - collide = true; - } else if (this.cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - try { - if (this.counterCells == as) {// Expand table unless stale - CounterCell[] rs = new CounterCell[n << 1]; - for (int i = 0; i < n; ++i) { - rs[i] = as[i]; - } - this.counterCells = rs; - } - } finally { - this.cellsBusy = 0; - } - collide = false; - continue; // Retry with expanded table - } - h ^= h << 13; // Rehash - h ^= h >>> 17; - h ^= h << 5; - } - else if (this.cellsBusy == 0 && this.counterCells == as && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean init = false; - try { // Initialize table - if (this.counterCells == as) { - CounterCell[] rs = new CounterCell[2]; - rs[h & 1] = new CounterCell(x); - this.counterCells = rs; - init = true; - } - } finally { - this.cellsBusy = 0; - } - if (init) { - break; - } - } - else if (U.compareAndSwapLong(this, BASECOUNT, v = this.baseCount, v + x)) - { - break; // Fall back on using base - } - } - hc.code = h; // Record index for next time - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long SIZECTL; - private static final long TRANSFERINDEX; - private static final long TRANSFERORIGIN; - private static final long BASECOUNT; - private static final long CELLSBUSY; - private static final long CELLVALUE; - private static final long ABASE; - private static final int ASHIFT; - - static { - try { - U = getUnsafe(); - Class k = ConcurrentHashMapV8.class; - SIZECTL = U.objectFieldOffset - (k.getDeclaredField("sizeCtl")); - TRANSFERINDEX = U.objectFieldOffset - (k.getDeclaredField("transferIndex")); - TRANSFERORIGIN = U.objectFieldOffset - (k.getDeclaredField("transferOrigin")); - BASECOUNT = U.objectFieldOffset - (k.getDeclaredField("baseCount")); - CELLSBUSY = U.objectFieldOffset - (k.getDeclaredField("cellsBusy")); - Class ck = CounterCell.class; - CELLVALUE = U.objectFieldOffset - (ck.getDeclaredField("value")); - Class ak = Node[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); - if ((scale & scale - 1) != 0) { - throw new Error("data type scale not a power of two"); - } - ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } + // private static final long serialVersionUID = 7249069246763182397L; +// +// /** +// * An object for traversing and partitioning elements of a source. +// * This interface provides a subset of the functionality of JDK8 +// * java.util.Spliterator. +// */ +// public static interface ConcurrentHashMapSpliterator { +// /** +// * If possible, returns a new spliterator covering +// * approximately one half of the elements, which will not be +// * covered by this spliterator. Returns null if cannot be +// * split. +// */ +// ConcurrentHashMapSpliterator trySplit(); +// /** +// * Returns an estimate of the number of elements covered by +// * this Spliterator. +// */ +// long estimateSize(); +// +// /** Applies the action to each untraversed element */ +// void forEachRemaining(Action action); +// /** If an element remains, applies the action and returns true. */ +// boolean tryAdvance(Action action); +// } +// +// // Sams +// /** Interface describing a void action of one argument */ +// public interface Action { void apply(A a); } +// /** Interface describing a void action of two arguments */ +// public interface BiAction { void apply(A a, B b); } +// /** Interface describing a function of one argument */ +// public interface Fun { T apply(A a); } +// /** Interface describing a function of two arguments */ +// public interface BiFun { T apply(A a, B b); } +// /** Interface describing a function mapping its argument to a double */ +// public interface ObjectToDouble { double apply(A a); } +// /** Interface describing a function mapping its argument to a long */ +// public interface ObjectToLong { long apply(A a); } +// /** Interface describing a function mapping its argument to an int */ +// public interface ObjectToInt {int apply(A a); } +// /** Interface describing a function mapping two arguments to a double */ +// public interface ObjectByObjectToDouble { double apply(A a, B b); } +// /** Interface describing a function mapping two arguments to a long */ +// public interface ObjectByObjectToLong { long apply(A a, B b); } +// /** Interface describing a function mapping two arguments to an int */ +// public interface ObjectByObjectToInt {int apply(A a, B b); } +// /** Interface describing a function mapping two doubles to a double */ +// public interface DoubleByDoubleToDouble { double apply(double a, double b); } +// /** Interface describing a function mapping two longs to a long */ +// public interface LongByLongToLong { long apply(long a, long b); } +// /** Interface describing a function mapping two ints to an int */ +// public interface IntByIntToInt { int apply(int a, int b); } +// +// /* +// * Overview: +// * +// * The primary design goal of this hash table is to maintain +// * concurrent readability (typically method getSubscriptions(), but also +// * iterators and related methods) while minimizing update +// * contention. Secondary goals are to keep space consumption about +// * the same or better than java.util.HashMap, and to support high +// * initial insertion rates on an empty table by many threads. +// * +// * This map usually acts as a binned (bucketed) hash table. Each +// * key-value mapping is held in a Node. Most nodes are instances +// * of the basic Node class with hash, key, value, and next +// * fields. However, various subclasses exist: TreeNodes are +// * arranged in balanced trees, not lists. TreeBins hold the roots +// * of sets of TreeNodes. ForwardingNodes are placed at the heads +// * of bins during resizing. ReservationNodes are used as +// * placeholders while establishing values in computeIfAbsent and +// * related methods. The types TreeBin, ForwardingNode, and +// * ReservationNode do not hold normal user keys, values, or +// * hashes, and are readily distinguishable during search etc +// * because they have negative hash fields and null key and value +// * fields. (These special nodes are either uncommon or transient, +// * so the impact of carrying around some unused fields is +// * insignificant.) +// * +// * The table is lazily initialized to a power-of-two size upon the +// * first insertion. Each bin in the table normally contains a +// * list of Nodes (most often, the list has only zero or one Node). +// * Table accesses require volatile/atomic reads, writes, and +// * CASes. Because there is no other way to arrange this without +// * adding further indirections, we use intrinsics +// * (sun.misc.Unsafe) operations. +// * +// * We use the top (sign) bit of Node hash fields for control +// * purposes -- it is available anyway because of addressing +// * constraints. Nodes with negative hash fields are specially +// * handled or ignored in map methods. +// * +// * Insertion (via put or its variants) of the first node in an +// * empty bin is performed by just CASing it to the bin. This is +// * by far the most common case for put operations under most +// * key/hash distributions. Other update operations (insert, +// * delete, and replace) require locks. We do not want to waste +// * the space required to associate a distinct lock object with +// * each bin, so instead use the first node of a bin list itself as +// * a lock. Locking support for these locks relies on builtin +// * "synchronized" monitors. +// * +// * Using the first node of a list as a lock does not by itself +// * suffice though: When a node is locked, any update must first +// * validate that it is still the first node after locking it, and +// * retry if not. Because new nodes are always appended to lists, +// * once a node is first in a bin, it remains first until deleted +// * or the bin becomes invalidated (upon resizing). +// * +// * The main disadvantage of per-bin locks is that other update +// * operations on other nodes in a bin list protected by the same +// * lock can stall, for example when user equals() or mapping +// * functions take a long time. However, statistically, under +// * random hash codes, this is not a common problem. Ideally, the +// * frequency of nodes in bins follows a Poisson distribution +// * (http://en.wikipedia.org/wiki/Poisson_distribution) with a +// * parameter of about 0.5 on average, given the resizing threshold +// * of 0.75, although with a large variance because of resizing +// * granularity. Ignoring variance, the expected occurrences of +// * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The +// * first values are: +// * +// * 0: 0.60653066 +// * 1: 0.30326533 +// * 2: 0.07581633 +// * 3: 0.01263606 +// * 4: 0.00157952 +// * 5: 0.00015795 +// * 6: 0.00001316 +// * 7: 0.00000094 +// * 8: 0.00000006 +// * more: less than 1 in ten million +// * +// * Lock contention probability for two threads accessing distinct +// * elements is roughly 1 / (8 * #elements) under random hashes. +// * +// * Actual hash code distributions encountered in practice +// * sometimes deviate significantly from uniform randomness. This +// * includes the case when N > (1<<30), so some keys MUST collide. +// * Similarly for dumb or hostile usages in which multiple keys are +// * designed to have identical hash codes or ones that differs only +// * in masked-out high bits. So we use a secondary strategy that +// * applies when the number of nodes in a bin exceeds a +// * threshold. These TreeBins use a balanced tree to hold nodes (a +// * specialized form of red-black trees), bounding search time to +// * O(log N). Each search step in a TreeBin is at least twice as +// * slow as in a regular list, but given that N cannot exceed +// * (1<<64) (before running out of addresses) this bounds search +// * steps, lock hold times, etc, to reasonable constants (roughly +// * 100 nodes inspected per operation worst case) so long as keys +// * are Comparable (which is very common -- String, Long, etc). +// * TreeBin nodes (TreeNodes) also maintain the same "next" +// * traversal pointers as regular nodes, so can be traversed in +// * iterators in the same way. +// * +// * The table is resized when occupancy exceeds a percentage +// * threshold (nominally, 0.75, but see below). Any thread +// * noticing an overfull bin may assist in resizing after the +// * initiating thread allocates and sets up the replacement +// * array. However, rather than stalling, these other threads may +// * proceed with insertions etc. The use of TreeBins shields us +// * from the worst case effects of overfilling while resizes are in +// * progress. Resizing proceeds by transferring bins, one by one, +// * from the table to the next table. To enable concurrency, the +// * next table must be (incrementally) prefilled with place-holders +// * serving as reverse forwarders to the old table. 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. On average, 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 concurrently +// * traversing table. Upon transfer, the old table bin contains +// * only a special forwarding node (with hash field "MOVED") that +// * contains the next table as its key. On encountering a +// * forwarding node, access and update operations restart, using +// * the new table. +// * +// * Each bin transfer requires its bin lock, which can stall +// * waiting for locks while resizing. However, because other +// * threads can join in and help resize rather than contend for +// * locks, average aggregate waits become shorter as resizing +// * progresses. The transfer operation must also ensure that all +// * accessible bins in both the old and new table are usable by any +// * traversal. This is arranged by proceeding from the last bin +// * (table.length - 1) up towards the first. Upon seeing a +// * forwarding node, traversals (see class Traverser) arrange to +// * move to the new table without revisiting nodes. However, to +// * ensure that no intervening nodes are skipped, bin splitting can +// * only begin after the associated reverse-forwarders are in +// * place. +// * +// * The traversal scheme also applies to partial traversals of +// * ranges of bins (via an alternate Traverser constructor) +// * to support partitioned aggregate operations. Also, read-only +// * operations give up if ever forwarded to a null table, which +// * provides support for shutdown-style clearing, which is also not +// * currently implemented. +// * +// * Lazy table initialization minimizes footprint until first use, +// * and also avoids resizings when the first operation is from a +// * putAll, constructor with map argument, or deserialization. +// * These cases attempt to override the initial capacity settings, +// * but harmlessly fail to take effect in cases of races. +// * +// * The element count is maintained using a specialization of +// * LongAdder. We need to incorporate a specialization rather than +// * just use a LongAdder in order to access implicit +// * contention-sensing that leads to creation of multiple +// * CounterCells. The counter mechanics avoid contention on +// * updates but can encounter cache thrashing if read too +// * frequently during concurrent access. To avoid reading so often, +// * resizing under contention is attempted only upon adding to a +// * bin already holding two or more nodes. Under uniform hash +// * distributions, the probability of this occurring at threshold +// * is around 13%, meaning that only about 1 in 8 puts check +// * threshold (and after resizing, many fewer do so). +// * +// * TreeBins use a special form of comparison for search and +// * related operations (which is the main reason we cannot use +// * existing collections such as TreeMaps). TreeBins contain +// * Comparable elements, but may contain others, as well as +// * elements that are Comparable but not necessarily Comparable for +// * the same T, so we cannot invoke compareTo among them. To handle +// * this, the tree is ordered primarily by hash value, then by +// * Comparable.compareTo order if applicable. On lookup at a node, +// * if elements are not comparable or compare as 0 then both left +// * and right children may need to be searched in the case of tied +// * hash values. (This corresponds to the full list search that +// * would be necessary if all elements were non-Comparable and had +// * tied hashes.) On insertion, to keep a total ordering (or as +// * close as is required here) across rebalancings, we compare +// * classes and identityHashCodes as tie-breakers. The red-black +// * balancing code is updated from pre-jdk-collections +// * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) +// * based in turn on Cormen, Leiserson, and Rivest "Introduction to +// * Algorithms" (CLR). +// * +// * TreeBins also require an additional locking mechanism. While +// * list traversal is always possible by readers even during +// * updates, tree traversal is not, mainly because of tree-rotations +// * that may change the root node and/or its linkages. TreeBins +// * include a simple read-write lock mechanism parasitic on the +// * main bin-synchronization strategy: Structural adjustments +// * associated with an insertion or removal are already bin-locked +// * (and so cannot conflict with other writers) but must wait for +// * ongoing readers to finish. Since there can be only one such +// * waiter, we use a simple scheme using a single "waiter" field to +// * block writers. However, readers need never block. If the root +// * lock is held, they proceed along the slow traversal path (via +// * next-pointers) until the lock becomes available or the list is +// * exhausted, whichever comes first. These cases are not fast, but +// * maximize aggregate expected throughput. +// * +// * Maintaining API and serialization compatibility with previous +// * versions of this class introduces several oddities. Mainly: We +// * leave untouched but unused constructor arguments refering to +// * concurrencyLevel. We accept a loadFactor constructor argument, +// * but apply it only to initial table capacity (which is the only +// * time that we can guarantee to honor it.) We also declare an +// * unused "Segment" class that is instantiated in minimal form +// * only when serializing. +// * +// * Also, solely for compatibility with previous versions of this +// * class, it extends AbstractMap, even though all of its methods +// * are overridden, so it is just useless baggage. +// * +// * This file is organized to make things a little easier to follow +// * while reading than they might otherwise: First the main static +// * declarations and utilities, then fields, then main public +// * methods (with a few factorings of multiple public methods into +// * internal ones), then sizing methods, trees, traversers, and +// * bulk operations. +// */ +// +// /* ---------------- Constants -------------- */ +// +// /** +// * The largest possible table capacity. This value must be +// * exactly 1<<30 to stay within Java array allocation and indexing +// * bounds for power of two table sizes, and is further required +// * because the top two bits of 32bit hash fields are used for +// * control purposes. +// */ +// private static final int MAXIMUM_CAPACITY = 1 << 30; +// +// /** +// * The default initial table capacity. Must be a power of 2 +// * (i.e., at least 1) and at most MAXIMUM_CAPACITY. +// */ +// private static final int DEFAULT_CAPACITY = 16; +// +// /** +// * The largest possible (non-power of two) array size. +// * Needed by toArray and related methods. +// */ +// static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +// +// /** +// * The default concurrency level for this table. Unused but +// * defined for compatibility with previous versions of this class. +// */ +// private static final int DEFAULT_CONCURRENCY_LEVEL = 16; +// +// /** +// * The load factor for this table. Overrides of this value in +// * constructors affect only the initial table capacity. The +// * actual floating point value isn't normally used -- it is +// * simpler to use expressions such as {@code n - (n >>> 2)} for +// * the associated resizing threshold. +// */ +// private static final float LOAD_FACTOR = 0.75f; +// +// /** +// * The bin count threshold for using a tree rather than list for a +// * bin. Bins are converted to trees when adding an element to a +// * bin with at least this many nodes. The value must be greater +// * than 2, and should be at least 8 to mesh with assumptions in +// * tree removal about conversion back to plain bins upon +// * shrinkage. +// */ +// static final int TREEIFY_THRESHOLD = 8; +// +// /** +// * The bin count threshold for untreeifying a (split) bin during a +// * resize operation. Should be less than TREEIFY_THRESHOLD, and at +// * most 6 to mesh with shrinkage detection under removal. +// */ +// static final int UNTREEIFY_THRESHOLD = 6; +// +// /** +// * The smallest table capacity for which bins may be treeified. +// * (Otherwise the table is resized if too many nodes in a bin.) +// * The value should be at least 4 * TREEIFY_THRESHOLD to avoid +// * conflicts between resizing and treeification thresholds. +// */ +// static final int MIN_TREEIFY_CAPACITY = 64; +// +// /** +// * Minimum number of rebinnings per transfer step. Ranges are +// * subdivided to allow multiple resizer threads. This value +// * serves as a lower bound to avoid resizers encountering +// * excessive memory contention. The value should be at least +// * DEFAULT_CAPACITY. +// */ +// private static final int MIN_TRANSFER_STRIDE = 16; +// +// /* +// * Encodings for Node hash fields. See above for explanation. +// */ +// static final int MOVED = -1; // hash for forwarding nodes +// static final int TREEBIN = -2; // hash for roots of trees +// static final int RESERVED = -3; // hash for transient reservations +// static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash +// +// /** Number of CPUS, to place bounds on some sizings */ +// static final int NCPU = Runtime.getRuntime().availableProcessors(); +// +// /** For serialization compatibility. */ +// private static final ObjectStreamField[] serialPersistentFields = { +// new ObjectStreamField("segments", Segment[].class), +// new ObjectStreamField("segmentMask", Integer.TYPE), +// new ObjectStreamField("segmentShift", Integer.TYPE) +// }; +// +// /* ---------------- Nodes -------------- */ +// +// /** +// * Key-value entry. This class is never exported out as a +// * user-mutable Map.Entry (i.e., one supporting setValue; see +// * MapEntry below), but can be used for read-only traversals used +// * in bulk tasks. Subclasses of Node with a negative hash field +// * are special, and contain null keys and values (but are never +// * exported). Otherwise, keys and vals are never null. +// */ +// static class Node implements Map.Entry { +// final int hash; +// final K key; +// volatile V val; +// volatile Node next; +// +// Node(int hash, K key, V val, Node next) { +// this.hash = hash; +// this.key = key; +// this.val = val; +// this.next = next; +// } +// +// @Override +// public final K getKey() { return this.key; } +// @Override +// public final V getValue() { return this.val; } +// @Override +// public final int hashCode() { return this.key.hashCode() ^ this.val.hashCode(); } +// @Override +// public final String toString(){ return this.key + "=" + this.val; } +// @Override +// public final V setValue(V value) { +// throw new UnsupportedOperationException(); +// } +// +// @Override +// public final boolean equals(Object o) { +// Object k, v, u; Map.Entry e; +// return o instanceof Map.Entry && +// (k = (e = (Map.Entry)o).getKey()) != null && +// (v = e.getValue()) != null && +// (k == this.key || k.equals(this.key)) && +// (v == (u = this.val) || v.equals(u)); +// } +// +// /** +// * Virtualized support for map.getSubscriptions(); overridden in subclasses. +// */ +// Node find(int h, Object k) { +// Node e = this; +// if (k != null) { +// do { +// K ek; +// if (e.hash == h && +// ((ek = e.key) == k || ek != null && k.equals(ek))) { +// return e; +// } +// } while ((e = e.next) != null); +// } +// return null; +// } +// } +// +// /* ---------------- Static utilities -------------- */ +// +// /** +// * Spreads (XORs) higher bits of hash to lower and also forces top +// * bit to 0. Because the table uses power-of-two masking, sets of +// * hashes that vary only in bits above the current mask will +// * always collide. (Among known examples are sets of Float keys +// * holding consecutive whole numbers in small tables.) So we +// * apply a transform that spreads the impact of higher bits +// * downward. There is a tradeoff between speed, utility, and +// * quality of bit-spreading. Because many common sets of hashes +// * are already reasonably distributed (so don't benefit from +// * spreading), and because we use trees to handle large sets of +// * collisions in bins, we just XOR some shifted bits in the +// * cheapest possible way to reduce systematic lossage, as well as +// * to incorporate impact of the highest bits that would otherwise +// * never be used in index calculations because of table bounds. +// */ +// static final int spread(int h) { +// return (h ^ h >>> 16) & HASH_BITS; +// } +// +// /** +// * Returns a power of two table size for the given desired capacity. +// * See Hackers Delight, sec 3.2 +// */ +// private static final int tableSizeFor(int c) { +// int n = c - 1; +// n |= n >>> 1; +// n |= n >>> 2; +// n |= n >>> 4; +// n |= n >>> 8; +// n |= n >>> 16; +// return n < 0 ? 1 : n >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : n + 1; +// } +// +// /** +// * Returns x's Class if it is of the form "class C implements +// * Comparable", else null. +// */ +// static Class comparableClassFor(Object x) { +// if (x instanceof Comparable) { +// Class c; Type[] ts, as; Type t; ParameterizedType p; +// if ((c = x.getClass()) == String.class) { +// return c; +// } +// if ((ts = c.getGenericInterfaces()) != null) { +// for (int i = 0; i < ts.length; ++i) { +// if ((t = ts[i]) instanceof ParameterizedType && +// (p = (ParameterizedType)t).getRawType() == +// Comparable.class && +// (as = p.getActualTypeArguments()) != null && +// as.length == 1 && as[0] == c) { +// return c; +// } +// } +// } +// } +// return null; +// } +// +// /** +// * Returns k.compareTo(x) if x matches kc (k's screened comparable +// * class), else 0. +// */ +// @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable +// static int compareComparables(Class kc, Object k, Object x) { +// return x == null || x.getClass() != kc ? 0 : +// ((Comparable)k).compareTo(x); +// } +// +// /* ---------------- Table element access -------------- */ +// +// /* +// * Volatile access methods are used for table elements as well as +// * elements of in-progress next table while resizing. All uses of +// * the tab arguments must be null checked by callers. All callers +// * also paranoically precheck that tab's length is not zero (or an +// * equivalent check), thus ensuring that any index argument taking +// * the form of a hash value anded with (length - 1) is a valid +// * index. Note that, to be correct wrt arbitrary concurrency +// * errors by users, these checks must operate on local variables, +// * which accounts for some odd-looking inline assignments below. +// * Note that calls to setTabAt always occur within locked regions, +// * and so in principle require only release ordering, not need +// * full volatile semantics, but are currently coded as volatile +// * writes to be conservative. +// */ +// +// @SuppressWarnings("unchecked") +// static final Node tabAt(Node[] tab, int i) { +// return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); +// } +// +// static final boolean casTabAt(Node[] tab, int i, +// Node c, Node v) { +// return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); +// } +// +// static final void setTabAt(Node[] tab, int i, Node v) { +// U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); +// } +// +// /* ---------------- Fields -------------- */ +// +// /** +// * The array of bins. Lazily initialized upon first insertion. +// * Size is always a power of two. Accessed directly by iterators. +// */ +// transient volatile Node[] table; +// +// /** +// * The next table to use; non-null only while resizing. +// */ +// private transient volatile Node[] nextTable; +// +// /** +// * Base counter value, used mainly when there is no contention, +// * but also as a fallback during table initialization +// * races. Updated via CAS. +// */ +// private transient volatile long baseCount; +// +// /** +// * Table initialization and resizing control. When negative, the +// * table is being initialized or resized: -1 for initialization, +// * else -(1 + the number of active resizing threads). Otherwise, +// * when table is null, holds the initial table size to use upon +// * creation, or 0 for default. After initialization, holds the +// * next element count value upon which to resize the table. +// */ +// private transient volatile int sizeCtl; +// +// /** +// * The next table index (plus one) to split while resizing. +// */ +// private transient volatile int transferIndex; +// +// /** +// * The least available table index to split while resizing. +// */ +// private transient volatile int transferOrigin; +// +// /** +// * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. +// */ +// private transient volatile int cellsBusy; +// +// /** +// * Table of counter cells. When non-null, size is a power of 2. +// */ +// private transient volatile CounterCell[] counterCells; +// +// // views +// private transient KeySetView keySet; +// private transient ValuesView values; +// private transient EntrySetView entrySet; +// +// +// /* ---------------- Public operations -------------- */ +// +// /** +// * Creates a new, empty map with the default initial table size (16). +// */ +// public ConcurrentHashMapV8() { +// } +// +// /** +// * Creates a new, empty map with an initial table size +// * accommodating the specified number of elements without the need +// * to dynamically resize. +// * +// * @param initialCapacity The implementation performs internal +// * sizing to accommodate this many elements. +// * @throws IllegalArgumentException if the initial capacity of +// * elements is negative +// */ +// public ConcurrentHashMapV8(int initialCapacity) { +// if (initialCapacity < 0) { +// throw new IllegalArgumentException(); +// } +// int cap = initialCapacity >= MAXIMUM_CAPACITY >>> 1 ? +// MAXIMUM_CAPACITY : +// tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); +// this.sizeCtl = cap; +// } +// +// /** +// * Creates a new map with the same mappings as the given map. +// * +// * @param m the map +// */ +// public ConcurrentHashMapV8(Map m) { +// this.sizeCtl = DEFAULT_CAPACITY; +// putAll(m); +// } +// +// /** +// * Creates a new, empty map with an initial table size based on +// * the given number of elements ({@code initialCapacity}) and +// * initial table density ({@code loadFactor}). +// * +// * @param initialCapacity the initial capacity. The implementation +// * performs internal sizing to accommodate this many elements, +// * given the specified load factor. +// * @param loadFactor the load factor (table density) for +// * establishing the initial table size +// * @throws IllegalArgumentException if the initial capacity of +// * elements is negative or the load factor is nonpositive +// * +// * @since 1.6 +// */ +// public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { +// this(initialCapacity, loadFactor, 1); +// } +// +// /** +// * Creates a new, empty map with an initial table size based on +// * the given number of elements ({@code initialCapacity}), table +// * density ({@code loadFactor}), and number of concurrently +// * updating threads ({@code concurrencyLevel}). +// * +// * @param initialCapacity the initial capacity. The implementation +// * performs internal sizing to accommodate this many elements, +// * given the specified load factor. +// * @param loadFactor the load factor (table density) for +// * establishing the initial table size +// * @param concurrencyLevel the estimated number of concurrently +// * updating threads. The implementation may use this value as +// * a sizing hint. +// * @throws IllegalArgumentException if the initial capacity is +// * negative or the load factor or concurrencyLevel are +// * nonpositive +// */ +// public ConcurrentHashMapV8(int initialCapacity, +// float loadFactor, int concurrencyLevel) { +// if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) { +// throw new IllegalArgumentException(); +// } +// if (initialCapacity < concurrencyLevel) +// { +// initialCapacity = concurrencyLevel; // as estimated threads +// } +// long size = (long)(1.0 + initialCapacity / loadFactor); +// int cap = size >= MAXIMUM_CAPACITY ? +// MAXIMUM_CAPACITY : tableSizeFor((int)size); +// this.sizeCtl = cap; +// } +// +// // Original (since JDK1.2) Map methods +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public int size() { +// long n = sumCount(); +// return n < 0L ? 0 : +// n > Integer.MAX_VALUE ? Integer.MAX_VALUE : +// (int)n; +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public boolean isEmpty() { +// return sumCount() <= 0L; // ignore transient negative values +// } +// +// /** +// * Returns the value to which the specified key is mapped, +// * or {@code null} if this map contains no mapping for the key. +// * +// *

More formally, if this map contains a mapping from a key +// * {@code k} to a value {@code v} such that {@code key.equals(k)}, +// * then this method returns {@code v}; otherwise it returns +// * {@code null}. (There can be at most one such mapping.) +// * +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public V getSubscriptions(Object key) { +// Node[] tab; Node e, p; int n, eh; K ek; +// int h = spread(key.hashCode()); +// if ((tab = this.table) != null && (n = tab.length) > 0 && +// (e = tabAt(tab, n - 1 & h)) != null) { +// if ((eh = e.hash) == h) { +// if ((ek = e.key) == key || ek != null && key.equals(ek)) { +// return e.val; +// } +// } +// else if (eh < 0) { +// return (p = e.find(h, key)) != null ? p.val : null; +// } +// while ((e = e.next) != null) { +// if (e.hash == h && +// ((ek = e.key) == key || ek != null && key.equals(ek))) { +// return e.val; +// } +// } +// } +// return null; +// } +// +// /** +// * Tests if the specified object is a key in this table. +// * +// * @param key possible key +// * @return {@code true} if and only if the specified object +// * is a key in this table, as determined by the +// * {@code equals} method; {@code false} otherwise +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public boolean containsKey(Object key) { +// return getSubscriptions(key) != null; +// } +// +// /** +// * Returns {@code true} if this map maps one or more keys to the +// * specified value. Note: This method may require a full traversal +// * of the map, and is much slower than method {@code containsKey}. +// * +// * @param value value whose presence in this map is to be tested +// * @return {@code true} if this map maps one or more keys to the +// * specified value +// * @throws NullPointerException if the specified value is null +// */ +// @Override +// public boolean containsValue(Object value) { +// if (value == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// V v; +// if ((v = p.val) == value || v != null && value.equals(v)) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * Maps the specified key to the specified value in this table. +// * Neither the key nor the value can be null. +// * +// *

The value can be retrieved by calling the {@code getSubscriptions} method +// * with a key that is equal to the original key. +// * +// * @param key key with which the specified value is to be associated +// * @param value value to be associated with the specified key +// * @return the previous value associated with {@code key}, or +// * {@code null} if there was no mapping for {@code key} +// * @throws NullPointerException if the specified key or value is null +// */ +// @Override +// public V put(K key, V value) { +// return putVal(key, value, false); +// } +// +// /** Implementation for put and putIfAbsent */ +// final V putVal(K key, V value, boolean onlyIfAbsent) { +// if (key == null || value == null) { +// throw new NullPointerException(); +// } +// int hash = spread(key.hashCode()); +// int binCount = 0; +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0) { +// tab = initTable(); +// } else if ((f = tabAt(tab, i = n - 1 & hash)) == null) { +// if (casTabAt(tab, i, null, +// new Node(hash, key, value, null))) +// { +// break; // no lock when adding to empty bin +// } +// } +// else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// V oldVal = null; +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// binCount = 1; +// for (Node e = f;; ++binCount) { +// K ek; +// if (e.hash == hash && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// oldVal = e.val; +// if (!onlyIfAbsent) { +// e.val = value; +// } +// break; +// } +// Node pred = e; +// if ((e = e.next) == null) { +// pred.next = new Node(hash, key, +// value, null); +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// Node p; +// binCount = 2; +// if ((p = ((TreeBin)f).putTreeVal(hash, key, +// value)) != null) { +// oldVal = p.val; +// if (!onlyIfAbsent) { +// p.val = value; +// } +// } +// } +// } +// } +// if (binCount != 0) { +// if (binCount >= TREEIFY_THRESHOLD) { +// treeifyBin(tab, i); +// } +// if (oldVal != null) { +// return oldVal; +// } +// break; +// } +// } +// } +// addCount(1L, binCount); +// return null; +// } +// +// /** +// * Copies all of the mappings from the specified map to this one. +// * These mappings replace any mappings that this map had for any of the +// * keys currently in the specified map. +// * +// * @param m mappings to be stored in this map +// */ +// @Override +// public void putAll(Map m) { +// tryPresize(m.size()); +// for (Map.Entry e : m.entrySet()) { +// putVal(e.getKey(), e.getValue(), false); +// } +// } +// +// /** +// * Removes the key (and its corresponding value) from this map. +// * This method does nothing if the key is not in the map. +// * +// * @param key the key that needs to be removed +// * @return the previous value associated with {@code key}, or +// * {@code null} if there was no mapping for {@code key} +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public V remove(Object key) { +// return replaceNode(key, null, null); +// } +// +// /** +// * Implementation for the four public remove/replace methods: +// * Replaces node value with v, conditional upon match of cv if +// * non-null. If resulting value is null, delete. +// */ +// final V replaceNode(Object key, V value, Object cv) { +// int hash = spread(key.hashCode()); +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0 || +// (f = tabAt(tab, i = n - 1 & hash)) == null) { +// break; +// } else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// V oldVal = null; +// boolean validated = false; +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// validated = true; +// for (Node e = f, pred = null;;) { +// K ek; +// if (e.hash == hash && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// V ev = e.val; +// if (cv == null || cv == ev || +// ev != null && cv.equals(ev)) { +// oldVal = ev; +// if (value != null) { +// e.val = value; +// } else if (pred != null) { +// pred.next = e.next; +// } else { +// setTabAt(tab, i, e.next); +// } +// } +// break; +// } +// pred = e; +// if ((e = e.next) == null) { +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// validated = true; +// TreeBin t = (TreeBin)f; +// TreeNode r, p; +// if ((r = t.root) != null && +// (p = r.findTreeNode(hash, key, null)) != null) { +// V pv = p.val; +// if (cv == null || cv == pv || +// pv != null && cv.equals(pv)) { +// oldVal = pv; +// if (value != null) { +// p.val = value; +// } else if (t.removeTreeNode(p)) { +// setTabAt(tab, i, untreeify(t.first)); +// } +// } +// } +// } +// } +// } +// if (validated) { +// if (oldVal != null) { +// if (value == null) { +// addCount(-1L, -1); +// } +// return oldVal; +// } +// break; +// } +// } +// } +// return null; +// } +// +// /** +// * Removes all of the mappings from this map. +// */ +// @Override +// public void shutdown() { +// long delta = 0L; // negative number of deletions +// int i = 0; +// Node[] tab = this.table; +// while (tab != null && i < tab.length) { +// int fh; +// Node f = tabAt(tab, i); +// if (f == null) { +// ++i; +// } else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// i = 0; // restart +// } +// else { +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// Node p = fh >= 0 ? f : +// f instanceof TreeBin ? +// ((TreeBin)f).first : null; +// while (p != null) { +// --delta; +// p = p.next; +// } +// setTabAt(tab, i++, null); +// } +// } +// } +// } +// if (delta != 0L) { +// addCount(delta, -1); +// } +// } +// +// /** +// * Returns a {@link Set} view of the keys contained in this map. +// * The set is backed by the map, so changes to the map are +// * reflected in the set, and vice-versa. The set supports element +// * removal, which removes the corresponding mapping from this map, +// * via the {@code Iterator.remove}, {@code Set.remove}, +// * {@code removeAll}, {@code retainAll}, and {@code shutdown} +// * operations. It does not support the {@code add} or +// * {@code addAll} operations. +// * +// *

The view's {@code iterator} is a "weakly consistent" iterator +// * that will never throw {@link 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 the set view +// */ +// @Override +// public KeySetView keySet() { +// KeySetView ks; +// return (ks = this.keySet) != null ? ks : (this.keySet = new KeySetView(this, null)); +// } +// +// /** +// * Returns a {@link Collection} view of the values contained in this map. +// * The collection is backed by the map, so changes to the map are +// * reflected in the collection, and vice-versa. The collection +// * supports element removal, which removes the corresponding +// * mapping from this map, via the {@code Iterator.remove}, +// * {@code Collection.remove}, {@code removeAll}, +// * {@code retainAll}, and {@code shutdown} operations. It does not +// * support the {@code add} or {@code addAll} operations. +// * +// *

The view's {@code iterator} is a "weakly consistent" iterator +// * that will never throw {@link 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 the collection view +// */ +// @Override +// public Collection values() { +// ValuesView vs; +// return (vs = this.values) != null ? vs : (this.values = new ValuesView(this)); +// } +// +// /** +// * Returns a {@link Set} view of the mappings contained in this map. +// * The set is backed by the map, so changes to the map are +// * reflected in the set, and vice-versa. The set supports element +// * removal, which removes the corresponding mapping from the map, +// * via the {@code Iterator.remove}, {@code Set.remove}, +// * {@code removeAll}, {@code retainAll}, and {@code shutdown} +// * operations. +// * +// *

The view's {@code iterator} is a "weakly consistent" iterator +// * that will never throw {@link 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 the set view +// */ +// @Override +// public Set> entrySet() { +// EntrySetView es; +// return (es = this.entrySet) != null ? es : (this.entrySet = new EntrySetView(this)); +// } +// +// /** +// * Returns the hash code value for this {@link Map}, i.e., +// * the sum of, for each key-value pair in the map, +// * {@code key.hashCode() ^ value.hashCode()}. +// * +// * @return the hash code value for this map +// */ +// @Override +// public int hashCode() { +// int h = 0; +// Node[] t; +// if ((t = this.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// h += p.key.hashCode() ^ p.val.hashCode(); +// } +// } +// return h; +// } +// +// /** +// * Returns a string representation of this map. The string +// * representation consists of a list of key-value mappings (in no +// * particular order) enclosed in braces ("{@code {}}"). Adjacent +// * mappings are separated by the characters {@code ", "} (comma +// * and space). Each key-value mapping is rendered as the key +// * followed by an equals sign ("{@code =}") followed by the +// * associated value. +// * +// * @return a string representation of this map +// */ +// @Override +// public String toString() { +// Node[] t; +// int f = (t = this.table) == null ? 0 : t.length; +// Traverser it = new Traverser(t, f, 0, f); +// StringBuilder sb = new StringBuilder(); +// sb.append('{'); +// Node p; +// if ((p = it.advance()) != null) { +// for (;;) { +// K k = p.key; +// V v = p.val; +// sb.append(k == this ? "(this Map)" : k); +// sb.append('='); +// sb.append(v == this ? "(this Map)" : v); +// if ((p = it.advance()) == null) { +// break; +// } +// sb.append(',').append(' '); +// } +// } +// return sb.append('}').toString(); +// } +// +// /** +// * Compares the specified object with this map for equality. +// * Returns {@code true} if the given object is a map with the same +// * mappings as this map. This operation may return misleading +// * results if either map is concurrently modified during execution +// * of this method. +// * +// * @param o object to be compared for equality with this map +// * @return {@code true} if the specified object is equal to this map +// */ +// @Override +// public boolean equals(Object o) { +// if (o != this) { +// if (!(o instanceof Map)) { +// return false; +// } +// Map m = (Map) o; +// Node[] t; +// int f = (t = this.table) == null ? 0 : t.length; +// Traverser it = new Traverser(t, f, 0, f); +// for (Node p; (p = it.advance()) != null; ) { +// V val = p.val; +// Object v = m.getSubscriptions(p.key); +// if (v == null || v != val && !v.equals(val)) { +// return false; +// } +// } +// for (Map.Entry e : m.entrySet()) { +// Object mk, mv, v; +// if ((mk = e.getKey()) == null || +// (mv = e.getValue()) == null || +// (v = getSubscriptions(mk)) == null || +// mv != v && !mv.equals(v)) { +// return false; +// } +// } +// } +// return true; +// } +// +// /** +// * Stripped-down version of helper class used in previous version, +// * declared for the sake of serialization compatibility +// */ +// static class Segment extends ReentrantLock implements Serializable { +// private static final long serialVersionUID = 2249069246763182397L; +// final float loadFactor; +// Segment(float lf) { this.loadFactor = lf; } +// } +// +// /** +// * Saves the state of the {@code ConcurrentHashMapV8} instance to a +// * stream (i.e., serializes it). +// * @param s the stream +// * @throws java.io.IOException if an I/O error occurs +// * @serialData +// * the key (Object) and value (Object) +// * for each key-value mapping, followed by a null pair. +// * The key-value mappings are emitted in no particular order. +// */ +// private void writeObject(java.io.ObjectOutputStream s) +// throws java.io.IOException { +// // For serialization compatibility +// // Emulate segment calculation from previous version of this class +// int sshift = 0; +// int ssize = 1; +// while (ssize < DEFAULT_CONCURRENCY_LEVEL) { +// ++sshift; +// ssize <<= 1; +// } +// int segmentShift = 32 - sshift; +// int segmentMask = ssize - 1; +// @SuppressWarnings("unchecked") Segment[] segments = (Segment[]) +// new Segment[DEFAULT_CONCURRENCY_LEVEL]; +// for (int i = 0; i < segments.length; ++i) { +// segments[i] = new Segment(LOAD_FACTOR); +// } +// s.putFields().put("segments", segments); +// s.putFields().put("segmentShift", segmentShift); +// s.putFields().put("segmentMask", segmentMask); +// s.writeFields(); +// +// Node[] t; +// if ((t = this.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// s.writeObject(p.key); +// s.writeObject(p.val); +// } +// } +// s.writeObject(null); +// s.writeObject(null); +// segments = null; // throw away +// } +// +// /** +// * Reconstitutes the instance from a stream (that is, deserializes it). +// * @param s the stream +// * @throws ClassNotFoundException if the class of a serialized object +// * could not be found +// * @throws java.io.IOException if an I/O error occurs +// */ +// private void readObject(java.io.ObjectInputStream s) +// throws java.io.IOException, ClassNotFoundException { +// /* +// * To improve performance in typical cases, we create nodes +// * while reading, then place in table once size is known. +// * However, we must also validate uniqueness and deal with +// * overpopulated bins while doing so, which requires +// * specialized versions of putVal mechanics. +// */ +// this.sizeCtl = -1; // force exclusion for table construction +// s.defaultReadObject(); +// long size = 0L; +// Node p = null; +// for (;;) { +// @SuppressWarnings("unchecked") K k = (K) s.readObject(); +// @SuppressWarnings("unchecked") V v = (V) s.readObject(); +// if (k != null && v != null) { +// p = new Node(spread(k.hashCode()), k, v, p); +// ++size; +// } else { +// break; +// } +// } +// if (size == 0L) { +// this.sizeCtl = 0; +// } else { +// int n; +// if (size >= MAXIMUM_CAPACITY >>> 1) { +// n = MAXIMUM_CAPACITY; +// } else { +// int sz = (int)size; +// n = tableSizeFor(sz + (sz >>> 1) + 1); +// } +// @SuppressWarnings({"rawtypes","unchecked"}) +// Node[] tab = new Node[n]; +// int mask = n - 1; +// long added = 0L; +// while (p != null) { +// boolean insertAtFront; +// Node next = p.next, first; +// int h = p.hash, j = h & mask; +// if ((first = tabAt(tab, j)) == null) { +// insertAtFront = true; +// } else { +// K k = p.key; +// if (first.hash < 0) { +// TreeBin t = (TreeBin)first; +// if (t.putTreeVal(h, k, p.val) == null) { +// ++added; +// } +// insertAtFront = false; +// } +// else { +// int binCount = 0; +// insertAtFront = true; +// Node q; K qk; +// for (q = first; q != null; q = q.next) { +// if (q.hash == h && +// ((qk = q.key) == k || +// qk != null && k.equals(qk))) { +// insertAtFront = false; +// break; +// } +// ++binCount; +// } +// if (insertAtFront && binCount >= TREEIFY_THRESHOLD) { +// insertAtFront = false; +// ++added; +// p.next = first; +// TreeNode hd = null, tl = null; +// for (q = p; q != null; q = q.next) { +// TreeNode t = new TreeNode +// (q.hash, q.key, q.val, null, null); +// if ((t.prev = tl) == null) { +// hd = t; +// } else { +// tl.next = t; +// } +// tl = t; +// } +// setTabAt(tab, j, new TreeBin(hd)); +// } +// } +// } +// if (insertAtFront) { +// ++added; +// p.next = first; +// setTabAt(tab, j, p); +// } +// p = next; +// } +// this.table = tab; +// this.sizeCtl = n - (n >>> 2); +// this.baseCount = added; +// } +// } +// +// // ConcurrentMap methods +// +// /** +// * {@inheritDoc} +// * +// * @return the previous value associated with the specified key, +// * or {@code null} if there was no mapping for the key +// * @throws NullPointerException if the specified key or value is null +// */ +// @Override +// public V putIfAbsent(K key, V value) { +// return putVal(key, value, true); +// } +// +// /** +// * {@inheritDoc} +// * +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public boolean remove(Object key, Object value) { +// if (key == null) { +// throw new NullPointerException(); +// } +// return value != null && replaceNode(key, null, value) != null; +// } +// +// /** +// * {@inheritDoc} +// * +// * @throws NullPointerException if any of the arguments are null +// */ +// @Override +// public boolean replace(K key, V oldValue, V newValue) { +// if (key == null || oldValue == null || newValue == null) { +// throw new NullPointerException(); +// } +// return replaceNode(key, newValue, oldValue) != null; +// } +// +// /** +// * {@inheritDoc} +// * +// * @return the previous value associated with the specified key, +// * or {@code null} if there was no mapping for the key +// * @throws NullPointerException if the specified key or value is null +// */ +// @Override +// public V replace(K key, V value) { +// if (key == null || value == null) { +// throw new NullPointerException(); +// } +// return replaceNode(key, value, null); +// } +// +// // Overrides of JDK8+ Map extension method defaults +// +// /** +// * Returns the value to which the specified key is mapped, or the +// * given default value if this map contains no mapping for the +// * key. +// * +// * @param key the key whose associated value is to be returned +// * @param defaultValue the value to return if this map contains +// * no mapping for the given key +// * @return the mapping for the key, if present; else the default value +// * @throws NullPointerException if the specified key is null +// */ +// public V getOrDefault(Object key, V defaultValue) { +// V v; +// return (v = getSubscriptions(key)) == null ? defaultValue : v; +// } +// +// public void forEach(BiAction action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// action.apply(p.key, p.val); +// } +// } +// } +// +// public void replaceAll(BiFun function) { +// if (function == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// V oldValue = p.val; +// for (K key = p.key;;) { +// V newValue = function.apply(key, oldValue); +// if (newValue == null) { +// throw new NullPointerException(); +// } +// if (replaceNode(key, newValue, oldValue) != null || +// (oldValue = getSubscriptions(key)) == null) { +// break; +// } +// } +// } +// } +// } +// +// /** +// * If the specified key is not already associated with a value, +// * attempts to compute its value using the given mapping function +// * and enters it into this map unless {@code null}. The entire +// * method invocation is performed atomically, so the function is +// * applied at most once per key. Some attempted update operations +// * on this map by other threads may be blocked while computation +// * is in progress, so the computation should be short and simple, +// * and must not attempt to update any other mappings of this map. +// * +// * @param key key with which the specified value is to be associated +// * @param mappingFunction the function to compute a value +// * @return the current (existing or computed) value associated with +// * the specified key, or null if the computed value is null +// * @throws NullPointerException if the specified key or mappingFunction +// * is null +// * @throws IllegalStateException if the computation detectably +// * attempts a recursive update to this map that would +// * otherwise never complete +// * @throws RuntimeException or Error if the mappingFunction does so, +// * in which case the mapping is left unestablished +// */ +// public V computeIfAbsent(K key, Fun mappingFunction) { +// if (key == null || mappingFunction == null) { +// throw new NullPointerException(); +// } +// int h = spread(key.hashCode()); +// V val = null; +// int binCount = 0; +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0) { +// tab = initTable(); +// } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { +// Node r = new ReservationNode(); +// synchronized (r) { +// if (casTabAt(tab, i, null, r)) { +// binCount = 1; +// Node node = null; +// try { +// if ((val = mappingFunction.apply(key)) != null) { +// node = new Node(h, key, val, null); +// } +// } finally { +// setTabAt(tab, i, node); +// } +// } +// } +// if (binCount != 0) { +// break; +// } +// } +// else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// boolean added = false; +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// binCount = 1; +// for (Node e = f;; ++binCount) { +// K ek; V ev; +// if (e.hash == h && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// val = e.val; +// break; +// } +// Node pred = e; +// if ((e = e.next) == null) { +// if ((val = mappingFunction.apply(key)) != null) { +// added = true; +// pred.next = new Node(h, key, val, null); +// } +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// binCount = 2; +// TreeBin t = (TreeBin)f; +// TreeNode r, p; +// if ((r = t.root) != null && +// (p = r.findTreeNode(h, key, null)) != null) { +// val = p.val; +// } else if ((val = mappingFunction.apply(key)) != null) { +// added = true; +// t.putTreeVal(h, key, val); +// } +// } +// } +// } +// if (binCount != 0) { +// if (binCount >= TREEIFY_THRESHOLD) { +// treeifyBin(tab, i); +// } +// if (!added) { +// return val; +// } +// break; +// } +// } +// } +// if (val != null) { +// addCount(1L, binCount); +// } +// return val; +// } +// +// /** +// * If the value for the specified key is present, attempts to +// * compute a new mapping given the key and its current mapped +// * value. The entire method invocation is performed atomically. +// * Some attempted update operations on this map by other threads +// * may be blocked while computation is in progress, so the +// * computation should be short and simple, and must not attempt to +// * update any other mappings of this map. +// * +// * @param key key with which a value may be associated +// * @param remappingFunction the function to compute a value +// * @return the new value associated with the specified key, or null if none +// * @throws NullPointerException if the specified key or remappingFunction +// * is null +// * @throws IllegalStateException if the computation detectably +// * attempts a recursive update to this map that would +// * otherwise never complete +// * @throws RuntimeException or Error if the remappingFunction does so, +// * in which case the mapping is unchanged +// */ +// public V computeIfPresent(K key, BiFun remappingFunction) { +// if (key == null || remappingFunction == null) { +// throw new NullPointerException(); +// } +// int h = spread(key.hashCode()); +// V val = null; +// int delta = 0; +// int binCount = 0; +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0) { +// tab = initTable(); +// } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { +// break; +// } else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// binCount = 1; +// for (Node e = f, pred = null;; ++binCount) { +// K ek; +// if (e.hash == h && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// val = remappingFunction.apply(key, e.val); +// if (val != null) { +// e.val = val; +// } else { +// delta = -1; +// Node en = e.next; +// if (pred != null) { +// pred.next = en; +// } else { +// setTabAt(tab, i, en); +// } +// } +// break; +// } +// pred = e; +// if ((e = e.next) == null) { +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// binCount = 2; +// TreeBin t = (TreeBin)f; +// TreeNode r, p; +// if ((r = t.root) != null && +// (p = r.findTreeNode(h, key, null)) != null) { +// val = remappingFunction.apply(key, p.val); +// if (val != null) { +// p.val = val; +// } else { +// delta = -1; +// if (t.removeTreeNode(p)) { +// setTabAt(tab, i, untreeify(t.first)); +// } +// } +// } +// } +// } +// } +// if (binCount != 0) { +// break; +// } +// } +// } +// if (delta != 0) { +// addCount(delta, binCount); +// } +// return val; +// } +// +// /** +// * Attempts to compute a mapping for the specified key and its +// * current mapped value (or {@code null} if there is no current +// * mapping). The entire method invocation is performed atomically. +// * Some attempted update operations on this map by other threads +// * may be blocked while computation is in progress, so the +// * computation should be short and simple, and must not attempt to +// * update any other mappings of this Map. +// * +// * @param key key with which the specified value is to be associated +// * @param remappingFunction the function to compute a value +// * @return the new value associated with the specified key, or null if none +// * @throws NullPointerException if the specified key or remappingFunction +// * is null +// * @throws IllegalStateException if the computation detectably +// * attempts a recursive update to this map that would +// * otherwise never complete +// * @throws RuntimeException or Error if the remappingFunction does so, +// * in which case the mapping is unchanged +// */ +// public V compute(K key, +// BiFun remappingFunction) { +// if (key == null || remappingFunction == null) { +// throw new NullPointerException(); +// } +// int h = spread(key.hashCode()); +// V val = null; +// int delta = 0; +// int binCount = 0; +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0) { +// tab = initTable(); +// } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { +// Node r = new ReservationNode(); +// synchronized (r) { +// if (casTabAt(tab, i, null, r)) { +// binCount = 1; +// Node node = null; +// try { +// if ((val = remappingFunction.apply(key, null)) != null) { +// delta = 1; +// node = new Node(h, key, val, null); +// } +// } finally { +// setTabAt(tab, i, node); +// } +// } +// } +// if (binCount != 0) { +// break; +// } +// } +// else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// binCount = 1; +// for (Node e = f, pred = null;; ++binCount) { +// K ek; +// if (e.hash == h && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// val = remappingFunction.apply(key, e.val); +// if (val != null) { +// e.val = val; +// } else { +// delta = -1; +// Node en = e.next; +// if (pred != null) { +// pred.next = en; +// } else { +// setTabAt(tab, i, en); +// } +// } +// break; +// } +// pred = e; +// if ((e = e.next) == null) { +// val = remappingFunction.apply(key, null); +// if (val != null) { +// delta = 1; +// pred.next = +// new Node(h, key, val, null); +// } +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// binCount = 1; +// TreeBin t = (TreeBin)f; +// TreeNode r, p; +// if ((r = t.root) != null) { +// p = r.findTreeNode(h, key, null); +// } else { +// p = null; +// } +// V pv = p == null ? null : p.val; +// val = remappingFunction.apply(key, pv); +// if (val != null) { +// if (p != null) { +// p.val = val; +// } else { +// delta = 1; +// t.putTreeVal(h, key, val); +// } +// } +// else if (p != null) { +// delta = -1; +// if (t.removeTreeNode(p)) { +// setTabAt(tab, i, untreeify(t.first)); +// } +// } +// } +// } +// } +// if (binCount != 0) { +// if (binCount >= TREEIFY_THRESHOLD) { +// treeifyBin(tab, i); +// } +// break; +// } +// } +// } +// if (delta != 0) { +// addCount(delta, binCount); +// } +// return val; +// } +// +// /** +// * If the specified key is not already associated with a +// * (non-null) value, associates it with the given value. +// * Otherwise, replaces the value with the results of the given +// * remapping function, or removes if {@code null}. The entire +// * method invocation is performed atomically. Some attempted +// * update operations on this map by other threads may be blocked +// * while computation is in progress, so the computation should be +// * short and simple, and must not attempt to update any other +// * mappings of this Map. +// * +// * @param key key with which the specified value is to be associated +// * @param value the value to use if absent +// * @param remappingFunction the function to recompute a value if present +// * @return the new value associated with the specified key, or null if none +// * @throws NullPointerException if the specified key or the +// * remappingFunction is null +// * @throws RuntimeException or Error if the remappingFunction does so, +// * in which case the mapping is unchanged +// */ +// public V merge(K key, V value, BiFun remappingFunction) { +// if (key == null || value == null || remappingFunction == null) { +// throw new NullPointerException(); +// } +// int h = spread(key.hashCode()); +// V val = null; +// int delta = 0; +// int binCount = 0; +// for (Node[] tab = this.table;;) { +// Node f; int n, i, fh; +// if (tab == null || (n = tab.length) == 0) { +// tab = initTable(); +// } else if ((f = tabAt(tab, i = n - 1 & h)) == null) { +// if (casTabAt(tab, i, null, new Node(h, key, value, null))) { +// delta = 1; +// val = value; +// break; +// } +// } +// else if ((fh = f.hash) == MOVED) { +// tab = helpTransfer(tab, f); +// } else { +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// if (fh >= 0) { +// binCount = 1; +// for (Node e = f, pred = null;; ++binCount) { +// K ek; +// if (e.hash == h && +// ((ek = e.key) == key || +// ek != null && key.equals(ek))) { +// val = remappingFunction.apply(e.val, value); +// if (val != null) { +// e.val = val; +// } else { +// delta = -1; +// Node en = e.next; +// if (pred != null) { +// pred.next = en; +// } else { +// setTabAt(tab, i, en); +// } +// } +// break; +// } +// pred = e; +// if ((e = e.next) == null) { +// delta = 1; +// val = value; +// pred.next = +// new Node(h, key, val, null); +// break; +// } +// } +// } +// else if (f instanceof TreeBin) { +// binCount = 2; +// TreeBin t = (TreeBin)f; +// TreeNode r = t.root; +// TreeNode p = r == null ? null : +// r.findTreeNode(h, key, null); +// val = p == null ? value : +// remappingFunction.apply(p.val, value); +// if (val != null) { +// if (p != null) { +// p.val = val; +// } else { +// delta = 1; +// t.putTreeVal(h, key, val); +// } +// } +// else if (p != null) { +// delta = -1; +// if (t.removeTreeNode(p)) { +// setTabAt(tab, i, untreeify(t.first)); +// } +// } +// } +// } +// } +// if (binCount != 0) { +// if (binCount >= TREEIFY_THRESHOLD) { +// treeifyBin(tab, i); +// } +// break; +// } +// } +// } +// if (delta != 0) { +// addCount(delta, binCount); +// } +// return val; +// } +// +// // Hashtable legacy methods +// +// /** +// * Legacy method testing if some key maps into the specified value +// * in this table. This method is identical in functionality to +// * {@link #containsValue(Object)}, 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 {@code true} if and only if some key maps to the +// * {@code value} argument in this table as +// * determined by the {@code equals} method; +// * {@code false} otherwise +// * @throws NullPointerException if the specified value is null +// */ +// @Deprecated public boolean contains(Object value) { +// return containsValue(value); +// } +// +// /** +// * Returns an enumeration of the keys in this table. +// * +// * @return an enumeration of the keys in this table +// * @see #keySet() +// */ +// public Enumeration keys() { +// Node[] t; +// int f = (t = this.table) == null ? 0 : t.length; +// return new KeyIterator(t, f, 0, f, this); +// } +// +// /** +// * Returns an enumeration of the values in this table. +// * +// * @return an enumeration of the values in this table +// * @see #values() +// */ +// public Enumeration elements() { +// Node[] t; +// int f = (t = this.table) == null ? 0 : t.length; +// return new ValueIterator(t, f, 0, f, this); +// } +// +// // ConcurrentHashMapV8-only methods +// +// /** +// * Returns the number of mappings. This method should be used +// * instead of {@link #size} because a ConcurrentHashMapV8 may +// * contain more mappings than can be represented as an int. The +// * value returned is an estimate; the actual count may differ if +// * there are concurrent insertions or removals. +// * +// * @return the number of mappings +// * @since 1.8 +// */ +// public long mappingCount() { +// long n = sumCount(); +// return n < 0L ? 0L : n; // ignore transient negative values +// } +// +// /** +// * Creates a new {@link Set} backed by a ConcurrentHashMapV8 +// * from the given type to {@code Boolean.TRUE}. +// * +// * @return the new set +// * @since 1.8 +// */ +// public static KeySetView newKeySet() { +// return new KeySetView +// (new ConcurrentHashMapV8(), Boolean.TRUE); +// } +// +// /** +// * Creates a new {@link Set} backed by a ConcurrentHashMapV8 +// * from the given type to {@code Boolean.TRUE}. +// * +// * @param initialCapacity The implementation performs internal +// * sizing to accommodate this many elements. +// * @return the new set +// * @throws IllegalArgumentException if the initial capacity of +// * elements is negative +// * @since 1.8 +// */ +// public static KeySetView newKeySet(int initialCapacity) { +// return new KeySetView +// (new ConcurrentHashMapV8(initialCapacity), Boolean.TRUE); +// } +// +// /** +// * Returns a {@link Set} view of the keys in this map, using the +// * given common mapped value for any additions (i.e., {@link +// * Collection#add} and {@link Collection#addAll(Collection)}). +// * This is of course only appropriate if it is acceptable to use +// * the same value for all additions from this view. +// * +// * @param mappedValue the mapped value to use for any additions +// * @return the set view +// * @throws NullPointerException if the mappedValue is null +// */ +// public KeySetView keySet(V mappedValue) { +// if (mappedValue == null) { +// throw new NullPointerException(); +// } +// return new KeySetView(this, mappedValue); +// } +// +// /* ---------------- Special Nodes -------------- */ +// +// /** +// * A node inserted at head of bins during transfer operations. +// */ +// static final class ForwardingNode extends Node { +// final Node[] nextTable; +// ForwardingNode(Node[] tab) { +// super(MOVED, null, null, null); +// this.nextTable = tab; +// } +// +// @Override +// Node find(int h, Object k) { +// // loop to avoid arbitrarily deep recursion on forwarding nodes +// outer: for (Node[] tab = this.nextTable;;) { +// Node e; int n; +// if (k == null || tab == null || (n = tab.length) == 0 || +// (e = tabAt(tab, n - 1 & h)) == null) { +// return null; +// } +// for (;;) { +// int eh; K ek; +// if ((eh = e.hash) == h && +// ((ek = e.key) == k || ek != null && k.equals(ek))) { +// return e; +// } +// if (eh < 0) { +// if (e instanceof ForwardingNode) { +// tab = ((ForwardingNode)e).nextTable; +// continue outer; +// } else { +// return e.find(h, k); +// } +// } +// if ((e = e.next) == null) { +// return null; +// } +// } +// } +// } +// } +// +// /** +// * A place-holder node used in computeIfAbsent and compute +// */ +// static final class ReservationNode extends Node { +// ReservationNode() { +// super(RESERVED, null, null, null); +// } +// +// @Override +// Node find(int h, Object k) { +// return null; +// } +// } +// +// /* ---------------- Table Initialization and Resizing -------------- */ +// +// /** +// * Initializes table, using the size recorded in sizeCtl. +// */ +// private final Node[] initTable() { +// Node[] tab; int sc; +// while ((tab = this.table) == null || tab.length == 0) { +// if ((sc = this.sizeCtl) < 0) { +// Thread.yield(); // lost initialization race; just spin +// } else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { +// try { +// if ((tab = this.table) == null || tab.length == 0) { +// int n = sc > 0 ? sc : DEFAULT_CAPACITY; +// @SuppressWarnings({"rawtypes","unchecked"}) +// Node[] nt = new Node[n]; +// this.table = tab = nt; +// sc = n - (n >>> 2); +// } +// } finally { +// this.sizeCtl = sc; +// } +// break; +// } +// } +// return tab; +// } +// +// /** +// * Adds to count, and if table is too small and not already +// * resizing, initiates transfer. If already resizing, helps +// * perform transfer if work is available. Rechecks occupancy +// * after a transfer to see if another resize is already needed +// * because resizings are lagging additions. +// * +// * @param x the count to add +// * @param check if <0, don't check resize, if <= 1 only check if uncontended +// */ +// private final void addCount(long x, int check) { +// CounterCell[] as; long b, s; +// if ((as = this.counterCells) != null || +// !U.compareAndSwapLong(this, BASECOUNT, b = this.baseCount, s = b + x)) { +// CounterHashCode hc; CounterCell a; long v; int m; +// boolean uncontended = true; +// if ((hc = threadCounterHashCode.getSubscriptions()) == null || +// as == null || (m = as.length - 1) < 0 || +// (a = as[m & hc.code]) == null || +// !(uncontended = +// U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { +// fullAddCount(x, hc, uncontended); +// return; +// } +// if (check <= 1) { +// return; +// } +// s = sumCount(); +// } +// if (check >= 0) { +// Node[] tab, nt; int sc; +// while (s >= (sc = this.sizeCtl) && (tab = this.table) != null && +// tab.length < MAXIMUM_CAPACITY) { +// if (sc < 0) { +// if (sc == -1 || this.transferIndex <= this.transferOrigin || +// (nt = this.nextTable) == null) { +// break; +// } +// if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { +// transfer(tab, nt); +// } +// } +// else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) { +// transfer(tab, null); +// } +// s = sumCount(); +// } +// } +// } +// +// /** +// * Helps transfer if a resize is in progress. +// */ +// final Node[] helpTransfer(Node[] tab, Node f) { +// Node[] nextTab; int sc; +// if (f instanceof ForwardingNode && +// (nextTab = ((ForwardingNode)f).nextTable) != null) { +// if (nextTab == this.nextTable && tab == this.table && +// this.transferIndex > this.transferOrigin && (sc = this.sizeCtl) < -1 && +// U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { +// transfer(tab, nextTab); +// } +// return nextTab; +// } +// return this.table; +// } +// +// /** +// * Tries to presize table to accommodate the given number of elements. +// * +// * @param size number of elements (doesn't need to be perfectly accurate) +// */ +// private final void tryPresize(int size) { +// int c = size >= MAXIMUM_CAPACITY >>> 1 ? MAXIMUM_CAPACITY : +// tableSizeFor(size + (size >>> 1) + 1); +// int sc; +// while ((sc = this.sizeCtl) >= 0) { +// Node[] tab = this.table; int n; +// if (tab == null || (n = tab.length) == 0) { +// n = sc > c ? sc : c; +// if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { +// try { +// if (this.table == tab) { +// @SuppressWarnings({"rawtypes","unchecked"}) +// Node[] nt = new Node[n]; +// this.table = nt; +// sc = n - (n >>> 2); +// } +// } finally { +// this.sizeCtl = sc; +// } +// } +// } +// else if (c <= sc || n >= MAXIMUM_CAPACITY) { +// break; +// } else if (tab == this.table && +// U.compareAndSwapInt(this, SIZECTL, sc, -2)) { +// transfer(tab, null); +// } +// } +// } +// +// /** +// * Moves and/or copies the nodes in each bin to new table. See +// * above for explanation. +// */ +// private final void transfer(Node[] tab, Node[] nextTab) { +// int n = tab.length, stride; +// if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) +// { +// stride = MIN_TRANSFER_STRIDE; // subdivide range +// } +// if (nextTab == null) { // initiating +// try { +// @SuppressWarnings({"rawtypes","unchecked"}) +// Node[] nt = new Node[n << 1]; +// nextTab = nt; +// } catch (Throwable ex) { // try to cope with OOME +// this.sizeCtl = Integer.MAX_VALUE; +// return; +// } +// this.nextTable = nextTab; +// this.transferOrigin = n; +// this.transferIndex = n; +// ForwardingNode rev = new ForwardingNode(tab); +// for (int k = n; k > 0;) { // progressively reveal ready slots +// int nextk = k > stride ? k - stride : 0; +// for (int m = nextk; m < k; ++m) { +// nextTab[m] = rev; +// } +// for (int m = n + nextk; m < n + k; ++m) { +// nextTab[m] = rev; +// } +// U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); +// } +// } +// int nextn = nextTab.length; +// ForwardingNode fwd = new ForwardingNode(nextTab); +// boolean advance = true; +// boolean finishing = false; // to ensure sweep before committing nextTab +// for (int i = 0, bound = 0;;) { +// int nextIndex, nextBound, fh; Node f; +// while (advance) { +// if (--i >= bound || finishing) { +// advance = false; +// } else if ((nextIndex = this.transferIndex) <= this.transferOrigin) { +// i = -1; +// advance = false; +// } +// else if (U.compareAndSwapInt +// (this, TRANSFERINDEX, nextIndex, +// nextBound = nextIndex > stride ? +// nextIndex - stride : 0)) { +// bound = nextBound; +// i = nextIndex - 1; +// advance = false; +// } +// } +// if (i < 0 || i >= n || i + n >= nextn) { +// if (finishing) { +// this.nextTable = null; +// this.table = nextTab; +// this.sizeCtl = (n << 1) - (n >>> 1); +// return; +// } +// for (int sc;;) { +// if (U.compareAndSwapInt(this, SIZECTL, sc = this.sizeCtl, ++sc)) { +// if (sc != -1) { +// return; +// } +// finishing = advance = true; +// i = n; // recheck before commit +// break; +// } +// } +// } +// else if ((f = tabAt(tab, i)) == null) { +// if (casTabAt(tab, i, null, fwd)) { +// setTabAt(nextTab, i, null); +// setTabAt(nextTab, i + n, null); +// advance = true; +// } +// } +// else if ((fh = f.hash) == MOVED) { +// advance = true; // already processed +// } else { +// synchronized (f) { +// if (tabAt(tab, i) == f) { +// Node ln, hn; +// if (fh >= 0) { +// int runBit = fh & n; +// Node lastRun = f; +// for (Node p = f.next; p != null; p = p.next) { +// int b = p.hash & n; +// if (b != runBit) { +// runBit = b; +// lastRun = p; +// } +// } +// if (runBit == 0) { +// ln = lastRun; +// hn = null; +// } +// else { +// hn = lastRun; +// ln = null; +// } +// for (Node p = f; p != lastRun; p = p.next) { +// int ph = p.hash; K pk = p.key; V pv = p.val; +// if ((ph & n) == 0) { +// ln = new Node(ph, pk, pv, ln); +// } else { +// hn = new Node(ph, pk, pv, hn); +// } +// } +// setTabAt(nextTab, i, ln); +// setTabAt(nextTab, i + n, hn); +// setTabAt(tab, i, fwd); +// advance = true; +// } +// else if (f instanceof TreeBin) { +// TreeBin t = (TreeBin)f; +// TreeNode lo = null, loTail = null; +// TreeNode hi = null, hiTail = null; +// int lc = 0, hc = 0; +// for (Node e = t.first; e != null; e = e.next) { +// int h = e.hash; +// TreeNode p = new TreeNode +// (h, e.key, e.val, null, null); +// if ((h & n) == 0) { +// if ((p.prev = loTail) == null) { +// lo = p; +// } else { +// loTail.next = p; +// } +// loTail = p; +// ++lc; +// } +// else { +// if ((p.prev = hiTail) == null) { +// hi = p; +// } else { +// hiTail.next = p; +// } +// hiTail = p; +// ++hc; +// } +// } +// ln = lc <= UNTREEIFY_THRESHOLD ? untreeify(lo) : +// hc != 0 ? new TreeBin(lo) : t; +// hn = hc <= UNTREEIFY_THRESHOLD ? untreeify(hi) : +// lc != 0 ? new TreeBin(hi) : t; +// setTabAt(nextTab, i, ln); +// setTabAt(nextTab, i + n, hn); +// setTabAt(tab, i, fwd); +// advance = true; +// } +// } +// } +// } +// } +// } +// +// /* ---------------- Conversion from/to TreeBins -------------- */ +// +// /** +// * Replaces all linked nodes in bin at given index unless table is +// * too small, in which case resizes instead. +// */ +// private final void treeifyBin(Node[] tab, int index) { +// Node b; int n, sc; +// if (tab != null) { +// if ((n = tab.length) < MIN_TREEIFY_CAPACITY) { +// if (tab == this.table && (sc = this.sizeCtl) >= 0 && +// U.compareAndSwapInt(this, SIZECTL, sc, -2)) { +// transfer(tab, null); +// } +// } +// else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { +// synchronized (b) { +// if (tabAt(tab, index) == b) { +// TreeNode hd = null, tl = null; +// for (Node e = b; e != null; e = e.next) { +// TreeNode p = +// new TreeNode(e.hash, e.key, e.val, +// null, null); +// if ((p.prev = tl) == null) { +// hd = p; +// } else { +// tl.next = p; +// } +// tl = p; +// } +// setTabAt(tab, index, new TreeBin(hd)); +// } +// } +// } +// } +// } +// +// /** +// * Returns a list on non-TreeNodes replacing those in given list. +// */ +// static Node untreeify(Node b) { +// Node hd = null, tl = null; +// for (Node q = b; q != null; q = q.next) { +// Node p = new Node(q.hash, q.key, q.val, null); +// if (tl == null) { +// hd = p; +// } else { +// tl.next = p; +// } +// tl = p; +// } +// return hd; +// } +// +// /* ---------------- TreeNodes -------------- */ +// +// /** +// * Nodes for use in TreeBins +// */ +// static final class TreeNode extends Node { +// TreeNode parent; // red-black tree links +// TreeNode left; +// TreeNode right; +// TreeNode prev; // needed to unlink next upon deletion +// boolean red; +// +// TreeNode(int hash, K key, V val, Node next, +// TreeNode parent) { +// super(hash, key, val, next); +// this.parent = parent; +// } +// +// @Override +// Node find(int h, Object k) { +// return findTreeNode(h, k, null); +// } +// +// /** +// * Returns the TreeNode (or null if not found) for the given key +// * starting at given root. +// */ +// final TreeNode findTreeNode(int h, Object k, Class kc) { +// if (k != null) { +// TreeNode p = this; +// do { +// int ph, dir; K pk; TreeNode q; +// TreeNode pl = p.left, pr = p.right; +// if ((ph = p.hash) > h) { +// p = pl; +// } else if (ph < h) { +// p = pr; +// } else if ((pk = p.key) == k || pk != null && k.equals(pk)) { +// return p; +// } else if (pl == null) { +// p = pr; +// } else if (pr == null) { +// p = pl; +// } else if ((kc != null || +// (kc = comparableClassFor(k)) != null) && +// (dir = compareComparables(kc, k, pk)) != 0) { +// p = dir < 0 ? pl : pr; +// } else if ((q = pr.findTreeNode(h, k, kc)) != null) { +// return q; +// } else { +// p = pl; +// } +// } while (p != null); +// } +// return null; +// } +// } +// +// /* ---------------- TreeBins -------------- */ +// +// /** +// * TreeNodes used at the heads of bins. TreeBins do not hold user +// * keys or values, but instead point to list of TreeNodes and +// * their root. They also maintain a parasitic read-write lock +// * forcing writers (who hold bin lock) to wait for readers (who do +// * not) to complete before tree restructuring operations. +// */ +// static final class TreeBin extends Node { +// TreeNode root; +// volatile TreeNode first; +// volatile Thread waiter; +// volatile int lockState; +// // values for lockState +// static final int WRITER = 1; // set while holding write lock +// static final int WAITER = 2; // set when waiting for write lock +// static final int READER = 4; // increment value for setting read lock +// +// /** +// * Tie-breaking utility for ordering insertions when equal +// * hashCodes and non-comparable. We don't require a total +// * order, just a consistent insertion rule to maintain +// * equivalence across rebalancings. Tie-breaking further than +// * necessary simplifies testing a bit. +// */ +// static int tieBreakOrder(Object a, Object b) { +// int d; +// if (a == null || b == null || +// (d = a.getClass().getName(). +// compareTo(b.getClass().getName())) == 0) { +// d = System.identityHashCode(a) <= System.identityHashCode(b) ? +// -1 : 1; +// } +// return d; +// } +// +// /** +// * Creates bin with initial set of nodes headed by b. +// */ +// TreeBin(TreeNode b) { +// super(TREEBIN, null, null, null); +// this.first = b; +// TreeNode r = null; +// for (TreeNode x = b, next; x != null; x = next) { +// next = (TreeNode)x.next; +// x.left = x.right = null; +// if (r == null) { +// x.parent = null; +// x.red = false; +// r = x; +// } +// else { +// K k = x.key; +// int h = x.hash; +// Class kc = null; +// for (TreeNode p = r;;) { +// int dir, ph; +// K pk = p.key; +// if ((ph = p.hash) > h) { +// dir = -1; +// } else if (ph < h) { +// dir = 1; +// } else if (kc == null && +// (kc = comparableClassFor(k)) == null || +// (dir = compareComparables(kc, k, pk)) == 0) { +// dir = tieBreakOrder(k, pk); +// } +// TreeNode xp = p; +// if ((p = dir <= 0 ? p.left : p.right) == null) { +// x.parent = xp; +// if (dir <= 0) { +// xp.left = x; +// } else { +// xp.right = x; +// } +// r = balanceInsertion(r, x); +// break; +// } +// } +// } +// } +// this.root = r; +// assert checkInvariants(this.root); +// } +// +// /** +// * Acquires write lock for tree restructuring. +// */ +// private final void lockRoot() { +// if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) +// { +// contendedLock(); // offload to separate method +// } +// } +// +// /** +// * Releases write lock for tree restructuring. +// */ +// private final void unlockRoot() { +// this.lockState = 0; +// } +// +// /** +// * Possibly blocks awaiting root lock. +// */ +// private final void contendedLock() { +// boolean waiting = false; +// for (int s;;) { +// if (((s = this.lockState) & WRITER) == 0) { +// if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { +// if (waiting) { +// this.waiter = null; +// } +// return; +// } +// } +// else if ((s | WAITER) == 0) { +// if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { +// waiting = true; +// this.waiter = Thread.currentThread(); +// } +// } +// else if (waiting) { +// LockSupport.park(this); +// } +// } +// } +// +// /** +// * Returns matching node or null if none. Tries to search +// * using tree comparisons from root, but continues linear +// * search when lock not available. +// */ +//@Override +//final Node find(int h, Object k) { +// if (k != null) { +// for (Node e = this.first; e != null; e = e.next) { +// int s; K ek; +// if (((s = this.lockState) & (WAITER|WRITER)) != 0) { +// if (e.hash == h && +// ((ek = e.key) == k || ek != null && k.equals(ek))) { +// return e; +// } +// } +// else if (U.compareAndSwapInt(this, LOCKSTATE, s, +// s + READER)) { +// TreeNode r, p; +// try { +// p = (r = this.root) == null ? null : +// r.findTreeNode(h, k, null); +// } finally { +// Thread w; +// int ls; +// do {} while (!U.compareAndSwapInt +// (this, LOCKSTATE, +// ls = this.lockState, ls - READER)); +// if (ls == (READER|WAITER) && (w = this.waiter) != null) { +// LockSupport.unpark(w); +// } +// } +// return p; +// } +// } +// } +// return null; +// } +// +// /** +// * Finds or adds a node. +// * @return null if added +// */ +// final TreeNode putTreeVal(int h, K k, V v) { +// Class kc = null; +// boolean searched = false; +// for (TreeNode p = this.root;;) { +// int dir, ph; K pk; +// if (p == null) { +// this.first = this.root = new TreeNode(h, k, v, null, null); +// break; +// } +// else if ((ph = p.hash) > h) { +// dir = -1; +// } else if (ph < h) { +// dir = 1; +// } else if ((pk = p.key) == k || pk != null && k.equals(pk)) { +// return p; +// } else if (kc == null && +// (kc = comparableClassFor(k)) == null || +// (dir = compareComparables(kc, k, pk)) == 0) { +// if (!searched) { +// TreeNode q, ch; +// searched = true; +// if ((ch = p.left) != null && +// (q = ch.findTreeNode(h, k, kc)) != null || +// (ch = p.right) != null && +// (q = ch.findTreeNode(h, k, kc)) != null) { +// return q; +// } +// } +// dir = tieBreakOrder(k, pk); +// } +// +// TreeNode xp = p; +// if ((p = dir <= 0 ? p.left : p.right) == null) { +// TreeNode x, f = this.first; +// this.first = x = new TreeNode(h, k, v, f, xp); +// if (f != null) { +// f.prev = x; +// } +// if (dir <= 0) { +// xp.left = x; +// } else { +// xp.right = x; +// } +// if (!xp.red) { +// x.red = true; +// } else { +// lockRoot(); +// try { +// this.root = balanceInsertion(this.root, x); +// } finally { +// unlockRoot(); +// } +// } +// break; +// } +// } +// assert checkInvariants(this.root); +// return null; +// } +// +// /** +// * Removes the given node, that must be present before this +// * call. This is messier than typical red-black deletion code +// * because we cannot swap the contents of an interior node +// * with a leaf successor that is pinned by "next" pointers +// * that are accessible independently of lock. So instead we +// * swap the tree linkages. +// * +// * @return true if now too small, so should be untreeified +// */ +// final boolean removeTreeNode(TreeNode p) { +// TreeNode next = (TreeNode)p.next; +// TreeNode pred = p.prev; // unlink traversal pointers +// TreeNode r, rl; +// if (pred == null) { +// this.first = next; +// } else { +// pred.next = next; +// } +// if (next != null) { +// next.prev = pred; +// } +// if (this.first == null) { +// this.root = null; +// return true; +// } +// if ((r = this.root) == null || r.right == null || // too small +// (rl = r.left) == null || rl.left == null) { +// return true; +// } +// lockRoot(); +// try { +// TreeNode replacement; +// TreeNode pl = p.left; +// TreeNode pr = p.right; +// if (pl != null && pr != null) { +// TreeNode s = pr, sl; +// while ((sl = s.left) != null) { +// s = sl; +// } +// boolean c = s.red; s.red = p.red; p.red = c; // swap colors +// TreeNode sr = s.right; +// TreeNode pp = p.parent; +// if (s == pr) { // p was s's direct parent +// p.parent = s; +// s.right = p; +// } +// else { +// TreeNode sp = s.parent; +// if ((p.parent = sp) != null) { +// if (s == sp.left) { +// sp.left = p; +// } else { +// sp.right = p; +// } +// } +// if ((s.right = pr) != null) { +// pr.parent = s; +// } +// } +// p.left = null; +// if ((p.right = sr) != null) { +// sr.parent = p; +// } +// if ((s.left = pl) != null) { +// pl.parent = s; +// } +// if ((s.parent = pp) == null) { +// r = s; +// } else if (p == pp.left) { +// pp.left = s; +// } else { +// pp.right = s; +// } +// if (sr != null) { +// replacement = sr; +// } else { +// replacement = p; +// } +// } +// else if (pl != null) { +// replacement = pl; +// } else if (pr != null) { +// replacement = pr; +// } else { +// replacement = p; +// } +// if (replacement != p) { +// TreeNode pp = replacement.parent = p.parent; +// if (pp == null) { +// r = replacement; +// } else if (p == pp.left) { +// pp.left = replacement; +// } else { +// pp.right = replacement; +// } +// p.left = p.right = p.parent = null; +// } +// +// this.root = p.red ? r : balanceDeletion(r, replacement); +// +// if (p == replacement) { // detach pointers +// TreeNode pp; +// if ((pp = p.parent) != null) { +// if (p == pp.left) { +// pp.left = null; +// } else if (p == pp.right) { +// pp.right = null; +// } +// p.parent = null; +// } +// } +// } finally { +// unlockRoot(); +// } +// assert checkInvariants(this.root); +// return false; +// } +// +// /* ------------------------------------------------------------ */ +// // Red-black tree methods, all adapted from CLR +// +// static TreeNode rotateLeft(TreeNode root, +// TreeNode p) { +// TreeNode r, pp, rl; +// if (p != null && (r = p.right) != null) { +// if ((rl = p.right = r.left) != null) { +// rl.parent = p; +// } +// if ((pp = r.parent = p.parent) == null) { +// (root = r).red = false; +// } else if (pp.left == p) { +// pp.left = r; +// } else { +// pp.right = r; +// } +// r.left = p; +// p.parent = r; +// } +// return root; +// } +// +// static TreeNode rotateRight(TreeNode root, +// TreeNode p) { +// TreeNode l, pp, lr; +// if (p != null && (l = p.left) != null) { +// if ((lr = p.left = l.right) != null) { +// lr.parent = p; +// } +// if ((pp = l.parent = p.parent) == null) { +// (root = l).red = false; +// } else if (pp.right == p) { +// pp.right = l; +// } else { +// pp.left = l; +// } +// l.right = p; +// p.parent = l; +// } +// return root; +// } +// +// static TreeNode balanceInsertion(TreeNode root, +// TreeNode x) { +// x.red = true; +// for (TreeNode xp, xpp, xppl, xppr;;) { +// if ((xp = x.parent) == null) { +// x.red = false; +// return x; +// } +// else if (!xp.red || (xpp = xp.parent) == null) { +// return root; +// } +// if (xp == (xppl = xpp.left)) { +// if ((xppr = xpp.right) != null && xppr.red) { +// xppr.red = false; +// xp.red = false; +// xpp.red = true; +// x = xpp; +// } +// else { +// if (x == xp.right) { +// root = rotateLeft(root, x = xp); +// xpp = (xp = x.parent) == null ? null : xp.parent; +// } +// if (xp != null) { +// xp.red = false; +// if (xpp != null) { +// xpp.red = true; +// root = rotateRight(root, xpp); +// } +// } +// } +// } +// else { +// if (xppl != null && xppl.red) { +// xppl.red = false; +// xp.red = false; +// xpp.red = true; +// x = xpp; +// } +// else { +// if (x == xp.left) { +// root = rotateRight(root, x = xp); +// xpp = (xp = x.parent) == null ? null : xp.parent; +// } +// if (xp != null) { +// xp.red = false; +// if (xpp != null) { +// xpp.red = true; +// root = rotateLeft(root, xpp); +// } +// } +// } +// } +// } +// } +// +// static TreeNode balanceDeletion(TreeNode root, +// TreeNode x) { +// for (TreeNode xp, xpl, xpr;;) { +// if (x == null || x == root) { +// return root; +// } else if ((xp = x.parent) == null) { +// x.red = false; +// return x; +// } +// else if (x.red) { +// x.red = false; +// return root; +// } +// else if ((xpl = xp.left) == x) { +// if ((xpr = xp.right) != null && xpr.red) { +// xpr.red = false; +// xp.red = true; +// root = rotateLeft(root, xp); +// xpr = (xp = x.parent) == null ? null : xp.right; +// } +// if (xpr == null) { +// x = xp; +// } else { +// TreeNode sl = xpr.left, sr = xpr.right; +// if ((sr == null || !sr.red) && +// (sl == null || !sl.red)) { +// xpr.red = true; +// x = xp; +// } +// else { +// if (sr == null || !sr.red) { +// if (sl != null) { +// sl.red = false; +// } +// xpr.red = true; +// root = rotateRight(root, xpr); +// xpr = (xp = x.parent) == null ? +// null : xp.right; +// } +// if (xpr != null) { +// xpr.red = xp == null ? false : xp.red; +// if ((sr = xpr.right) != null) { +// sr.red = false; +// } +// } +// if (xp != null) { +// xp.red = false; +// root = rotateLeft(root, xp); +// } +// x = root; +// } +// } +// } +// else { // symmetric +// if (xpl != null && xpl.red) { +// xpl.red = false; +// xp.red = true; +// root = rotateRight(root, xp); +// xpl = (xp = x.parent) == null ? null : xp.left; +// } +// if (xpl == null) { +// x = xp; +// } else { +// TreeNode sl = xpl.left, sr = xpl.right; +// if ((sl == null || !sl.red) && +// (sr == null || !sr.red)) { +// xpl.red = true; +// x = xp; +// } +// else { +// if (sl == null || !sl.red) { +// if (sr != null) { +// sr.red = false; +// } +// xpl.red = true; +// root = rotateLeft(root, xpl); +// xpl = (xp = x.parent) == null ? +// null : xp.left; +// } +// if (xpl != null) { +// xpl.red = xp == null ? false : xp.red; +// if ((sl = xpl.left) != null) { +// sl.red = false; +// } +// } +// if (xp != null) { +// xp.red = false; +// root = rotateRight(root, xp); +// } +// x = root; +// } +// } +// } +// } +// } +// +// /** +// * Recursive invariant check +// */ +// static boolean checkInvariants(TreeNode t) { +// TreeNode tp = t.parent, tl = t.left, tr = t.right, +// tb = t.prev, tn = (TreeNode)t.next; +// if (tb != null && tb.next != t) { +// return false; +// } +// if (tn != null && tn.prev != t) { +// return false; +// } +// if (tp != null && t != tp.left && t != tp.right) { +// return false; +// } +// if (tl != null && (tl.parent != t || tl.hash > t.hash)) { +// return false; +// } +// if (tr != null && (tr.parent != t || tr.hash < t.hash)) { +// return false; +// } +// if (t.red && tl != null && tl.red && tr != null && tr.red) { +// return false; +// } +// if (tl != null && !checkInvariants(tl)) { +// return false; +// } +// if (tr != null && !checkInvariants(tr)) { +// return false; +// } +// return true; +// } +// +// private static final sun.misc.Unsafe U; +// private static final long LOCKSTATE; +// static { +// try { +// U = getUnsafe(); +// Class k = TreeBin.class; +// LOCKSTATE = U.objectFieldOffset +// (k.getDeclaredField("lockState")); +// } catch (Exception e) { +// throw new Error(e); +// } +// } +// } +// +// /* ----------------Table Traversal -------------- */ +// +// /** +// * Encapsulates traversal for methods such as containsValue; also +// * serves as a base class for other iterators and spliterators. +// * +// * Method advance visits once each still-valid node that was +// * reachable upon iterator construction. It might miss some that +// * were added to a bin after the bin was visited, which is OK wrt +// * consistency guarantees. Maintaining this property in the face +// * of possible ongoing resizes requires a fair amount of +// * bookkeeping state that is difficult to optimize away amidst +// * volatile accesses. Even so, traversal maintains reasonable +// * throughput. +// * +// * Normally, iteration proceeds bin-by-bin traversing lists. +// * However, if the table has been resized, then all future steps +// * must traverse both the bin at the current index as well as at +// * (index + baseSize); and so on for further resizings. To +// * paranoically cope with potential sharing by users of iterators +// * across threads, iteration terminates if a bounds checks fails +// * for a table read. +// */ +// static class Traverser { +// Node[] tab; // current table; updated if resized +// Node next; // the next entry to use +// int index; // index of bin to use next +// int baseIndex; // current index of initial table +// int baseLimit; // index bound for initial table +// final int baseSize; // initial table size +// +// Traverser(Node[] tab, int size, int index, int limit) { +// this.tab = tab; +// this.baseSize = size; +// this.baseIndex = this.index = index; +// this.baseLimit = limit; +// this.next = null; +// } +// +// /** +// * Advances if possible, returning next valid node, or null if none. +// */ +// final Node advance() { +// Node e; +// if ((e = this.next) != null) { +// e = e.next; +// } +// for (;;) { +// Node[] t; int i, n; K ek; // must use locals in checks +// if (e != null) { +// return this.next = e; +// } +// if (this.baseIndex >= this.baseLimit || (t = this.tab) == null || +// (n = t.length) <= (i = this.index) || i < 0) { +// return this.next = null; +// } +// if ((e = tabAt(t, this.index)) != null && e.hash < 0) { +// if (e instanceof ForwardingNode) { +// this.tab = ((ForwardingNode)e).nextTable; +// e = null; +// continue; +// } +// else if (e instanceof TreeBin) { +// e = ((TreeBin)e).first; +// } else { +// e = null; +// } +// } +// if ((this.index += this.baseSize) >= n) +// { +// this.index = ++this.baseIndex; // visit upper slots if present +// } +// } +// } +// } +// +// /** +// * Base of key, value, and entry Iterators. Adds fields to +// * Traverser to support iterator.remove. +// */ +// static class BaseIterator extends Traverser { +// final ConcurrentHashMapV8 map; +// Node lastReturned; +// BaseIterator(Node[] tab, int size, int index, int limit, +// ConcurrentHashMapV8 map) { +// super(tab, size, index, limit); +// this.map = map; +// advance(); +// } +// +// public final boolean hasNext() { return this.next != null; } +// public final boolean hasMoreElements() { return this.next != null; } +// +// public final void remove() { +// Node p; +// if ((p = this.lastReturned) == null) { +// throw new IllegalStateException(); +// } +// this.lastReturned = null; +// this.map.replaceNode(p.key, null, null); +// } +// } +// +// static final class KeyIterator extends BaseIterator +// implements Iterator, Enumeration { +// KeyIterator(Node[] tab, int index, int size, int limit, +// ConcurrentHashMapV8 map) { +// super(tab, index, size, limit, map); +// } +// +// @Override +// public final K next() { +// Node p; +// if ((p = this.next) == null) { +// throw new NoSuchElementException(); +// } +// K k = p.key; +// this.lastReturned = p; +// advance(); +// return k; +// } +// +// @Override +// public final K nextElement() { return next(); } +// } +// +// static final class ValueIterator extends BaseIterator +// implements Iterator, Enumeration { +// ValueIterator(Node[] tab, int index, int size, int limit, +// ConcurrentHashMapV8 map) { +// super(tab, index, size, limit, map); +// } +// +// @Override +// public final V next() { +// Node p; +// if ((p = this.next) == null) { +// throw new NoSuchElementException(); +// } +// V v = p.val; +// this.lastReturned = p; +// advance(); +// return v; +// } +// +// @Override +// public final V nextElement() { return next(); } +// } +// +// static final class EntryIterator extends BaseIterator +// implements Iterator> { +// EntryIterator(Node[] tab, int index, int size, int limit, +// ConcurrentHashMapV8 map) { +// super(tab, index, size, limit, map); +// } +// +// @Override +// public final Map.Entry next() { +// Node p; +// if ((p = this.next) == null) { +// throw new NoSuchElementException(); +// } +// K k = p.key; +// V v = p.val; +// this.lastReturned = p; +// advance(); +// return new MapEntry(k, v, this.map); +// } +// } +// +// /** +// * Exported Entry for EntryIterator +// */ +// static final class MapEntry implements Map.Entry { +// final K key; // non-null +// V val; // non-null +// final ConcurrentHashMapV8 map; +// MapEntry(K key, V val, ConcurrentHashMapV8 map) { +// this.key = key; +// this.val = val; +// this.map = map; +// } +// @Override +// public K getKey() { return this.key; } +// @Override +// public V getValue() { return this.val; } +// @Override +// public int hashCode() { return this.key.hashCode() ^ this.val.hashCode(); } +// @Override +// public String toString() { return this.key + "=" + this.val; } +// +// @Override +// public boolean equals(Object o) { +// Object k, v; Map.Entry e; +// return o instanceof Map.Entry && +// (k = (e = (Map.Entry)o).getKey()) != null && +// (v = e.getValue()) != null && +// (k == this.key || k.equals(this.key)) && +// (v == this.val || v.equals(this.val)); +// } +// +// /** +// * Sets our entry's value and writes through to the map. The +// * value to return is somewhat arbitrary here. Since we do not +// * necessarily track asynchronous changes, the most recent +// * "previous" value could be different from what we return (or +// * could even have been removed, in which case the put will +// * re-establish). We do not and cannot guarantee more. +// */ +// @Override +// public V setValue(V value) { +// if (value == null) { +// throw new NullPointerException(); +// } +// V v = this.val; +// this.val = value; +// this.map.put(this.key, value); +// return v; +// } +// } +// +// static final class KeySpliterator extends Traverser +// implements ConcurrentHashMapSpliterator { +// long est; // size estimate +// KeySpliterator(Node[] tab, int size, int index, int limit, +// long est) { +// super(tab, size, index, limit); +// this.est = est; +// } +// +// @Override +// public ConcurrentHashMapSpliterator trySplit() { +// int i, f, h; +// return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : +// new KeySpliterator(this.tab, this.baseSize, this.baseLimit = h, +// f, this.est >>>= 1); +// } +// +// @Override +// public void forEachRemaining(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// for (Node p; (p = advance()) != null;) { +// action.apply(p.key); +// } +// } +// +// @Override +// public boolean tryAdvance(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node p; +// if ((p = advance()) == null) { +// return false; +// } +// action.apply(p.key); +// return true; +// } +// +// @Override +// public long estimateSize() { return this.est; } +// +// } +// +// static final class ValueSpliterator extends Traverser +// implements ConcurrentHashMapSpliterator { +// long est; // size estimate +// ValueSpliterator(Node[] tab, int size, int index, int limit, +// long est) { +// super(tab, size, index, limit); +// this.est = est; +// } +// +// @Override +// public ConcurrentHashMapSpliterator trySplit() { +// int i, f, h; +// return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : +// new ValueSpliterator(this.tab, this.baseSize, this.baseLimit = h, +// f, this.est >>>= 1); +// } +// +// @Override +// public void forEachRemaining(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// for (Node p; (p = advance()) != null;) { +// action.apply(p.val); +// } +// } +// +// @Override +// public boolean tryAdvance(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node p; +// if ((p = advance()) == null) { +// return false; +// } +// action.apply(p.val); +// return true; +// } +// +// @Override +// public long estimateSize() { return this.est; } +// +// } +// +// static final class EntrySpliterator extends Traverser +// implements ConcurrentHashMapSpliterator> { +// final ConcurrentHashMapV8 map; // To export MapEntry +// long est; // size estimate +// EntrySpliterator(Node[] tab, int size, int index, int limit, +// long est, ConcurrentHashMapV8 map) { +// super(tab, size, index, limit); +// this.map = map; +// this.est = est; +// } +// +// @Override +// public ConcurrentHashMapSpliterator> trySplit() { +// int i, f, h; +// return (h = (i = this.baseIndex) + (f = this.baseLimit) >>> 1) <= i ? null : +// new EntrySpliterator(this.tab, this.baseSize, this.baseLimit = h, +// f, this.est >>>= 1, this.map); +// } +// +// @Override +// public void forEachRemaining(Action> action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// for (Node p; (p = advance()) != null; ) { +// action.apply(new MapEntry(p.key, p.val, this.map)); +// } +// } +// +// @Override +// public boolean tryAdvance(Action> action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node p; +// if ((p = advance()) == null) { +// return false; +// } +// action.apply(new MapEntry(p.key, p.val, this.map)); +// return true; +// } +// +// @Override +// public long estimateSize() { return this.est; } +// +// } +// +// /* ----------------Views -------------- */ +// +// /** +// * Base class for views. +// */ +// abstract static class CollectionView +// implements Collection, java.io.Serializable { +// private static final long serialVersionUID = 7249069246763182397L; +// final ConcurrentHashMapV8 map; +// CollectionView(ConcurrentHashMapV8 map) { this.map = map; } +// +// /** +// * Returns the map backing this view. +// * +// * @return the map backing this view +// */ +// public ConcurrentHashMapV8 getMap() { return this.map; } +// +// /** +// * Removes all of the elements from this view, by removing all +// * the mappings from the map backing this view. +// */ +// @Override +// public final void shutdown() { this.map.shutdown(); } +// @Override +// public final int size() { return this.map.size(); } +// @Override +// public final boolean isEmpty() { return this.map.isEmpty(); } +// +// // implementations below rely on concrete classes supplying these +// // abstract methods +// /** +// * Returns a "weakly consistent" iterator that will never +// * throw {@link ConcurrentModificationException}, and +// * guarantees to traverse elements as they existed upon +// * construction of the iterator, and may (but is not +// * guaranteed to) reflect any modifications subsequent to +// * construction. +// */ +// @Override +// public abstract Iterator iterator(); +// @Override +// public abstract boolean contains(Object o); +// @Override +// public abstract boolean remove(Object o); +// +// private static final String oomeMsg = "Required array size too large"; +// +// @Override +// public final Object[] toArray() { +// long sz = this.map.mappingCount(); +// if (sz > MAX_ARRAY_SIZE) { +// throw new OutOfMemoryError(oomeMsg); +// } +// int n = (int)sz; +// Object[] r = new Object[n]; +// int i = 0; +// for (E e : this) { +// if (i == n) { +// if (n >= MAX_ARRAY_SIZE) { +// throw new OutOfMemoryError(oomeMsg); +// } +// if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { +// n = MAX_ARRAY_SIZE; +// } else { +// n += (n >>> 1) + 1; +// } +// r = Arrays.copyOf(r, n); +// } +// r[i++] = e; +// } +// return i == n ? r : Arrays.copyOf(r, i); +// } +// +// @Override +// @SuppressWarnings("unchecked") +// public final T[] toArray(T[] a) { +// long sz = this.map.mappingCount(); +// if (sz > MAX_ARRAY_SIZE) { +// throw new OutOfMemoryError(oomeMsg); +// } +// int m = (int)sz; +// T[] r = a.length >= m ? a : +// (T[])java.lang.reflect.Array +// .newInstance(a.getClass().getComponentType(), m); +// int n = r.length; +// int i = 0; +// for (E e : this) { +// if (i == n) { +// if (n >= MAX_ARRAY_SIZE) { +// throw new OutOfMemoryError(oomeMsg); +// } +// if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { +// n = MAX_ARRAY_SIZE; +// } else { +// n += (n >>> 1) + 1; +// } +// r = Arrays.copyOf(r, n); +// } +// r[i++] = (T)e; +// } +// if (a == r && i < n) { +// r[i] = null; // null-terminate +// return r; +// } +// return i == n ? r : Arrays.copyOf(r, i); +// } +// +// /** +// * Returns a string representation of this collection. +// * The string representation consists of the string representations +// * of the collection's elements in the order they are returned by +// * its iterator, enclosed in square brackets ({@code "[]"}). +// * Adjacent elements are separated by the characters {@code ", "} +// * (comma and space). Elements are converted to strings as by +// * {@link String#valueOf(Object)}. +// * +// * @return a string representation of this collection +// */ +// @Override +// public final String toString() { +// StringBuilder sb = new StringBuilder(); +// sb.append('['); +// Iterator it = iterator(); +// if (it.hasNext()) { +// for (;;) { +// Object e = it.next(); +// sb.append(e == this ? "(this Collection)" : e); +// if (!it.hasNext()) { +// break; +// } +// sb.append(',').append(' '); +// } +// } +// return sb.append(']').toString(); +// } +// +// @Override +// public final boolean containsAll(Collection c) { +// if (c != this) { +// for (Object e : c) { +// if (e == null || !contains(e)) { +// return false; +// } +// } +// } +// return true; +// } +// +// @Override +// public final boolean removeAll(Collection c) { +// boolean modified = false; +// for (Iterator it = iterator(); it.hasNext();) { +// if (c.contains(it.next())) { +// it.remove(); +// modified = true; +// } +// } +// return modified; +// } +// +// @Override +// public final boolean retainAll(Collection c) { +// boolean modified = false; +// for (Iterator it = iterator(); it.hasNext();) { +// if (!c.contains(it.next())) { +// it.remove(); +// modified = true; +// } +// } +// return modified; +// } +// +// } +// +// /** +// * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in +// * which additions may optionally be enabled by mapping to a +// * common value. This class cannot be directly instantiated. +// * See {@link #keySet() keySet()}, +// * {@link #keySet(Object) keySet(V)}, +// * {@link #newKeySet() newKeySet()}, +// * {@link #newKeySet(int) newKeySet(int)}. +// * +// * @since 1.8 +// */ +// public static class KeySetView extends CollectionView +// implements Set, java.io.Serializable { +// private static final long serialVersionUID = 7249069246763182397L; +// private final V value; +// KeySetView(ConcurrentHashMapV8 map, V value) { // non-public +// super(map); +// this.value = value; +// } +// +// /** +// * Returns the default mapped value for additions, +// * or {@code null} if additions are not supported. +// * +// * @return the default mapped value for additions, or {@code null} +// * if not supported +// */ +// public V getMappedValue() { return this.value; } +// +// /** +// * {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public boolean contains(Object o) { return this.map.containsKey(o); } +// +// /** +// * Removes the key from this map view, by removing the key (and its +// * corresponding value) from the backing map. This method does +// * nothing if the key is not in the map. +// * +// * @param o the key to be removed from the backing map +// * @return {@code true} if the backing map contained the specified key +// * @throws NullPointerException if the specified key is null +// */ +// @Override +// public boolean remove(Object o) { return this.map.remove(o) != null; } +// +// /** +// * @return an iterator over the keys of the backing map +// */ +// @Override +// public Iterator iterator() { +// Node[] t; +// ConcurrentHashMapV8 m = this.map; +// int f = (t = m.table) == null ? 0 : t.length; +// return new KeyIterator(t, f, 0, f, m); +// } +// +// /** +// * Adds the specified key to this set view by mapping the key to +// * the default mapped value in the backing map, if defined. +// * +// * @param e key to be added +// * @return {@code true} if this set changed as a result of the call +// * @throws NullPointerException if the specified key is null +// * @throws UnsupportedOperationException if no default mapped value +// * for additions was provided +// */ +// @Override +// public boolean add(K e) { +// V v; +// if ((v = this.value) == null) { +// throw new UnsupportedOperationException(); +// } +// return this.map.putVal(e, v, true) == null; +// } +// +// /** +// * Adds all of the elements in the specified collection to this set, +// * as if by calling {@link #add} on each one. +// * +// * @param c the elements to be inserted into this set +// * @return {@code true} if this set changed as a result of the call +// * @throws NullPointerException if the collection or any of its +// * elements are {@code null} +// * @throws UnsupportedOperationException if no default mapped value +// * for additions was provided +// */ +// @Override +// public boolean addAll(Collection c) { +// boolean added = false; +// V v; +// if ((v = this.value) == null) { +// throw new UnsupportedOperationException(); +// } +// for (K e : c) { +// if (this.map.putVal(e, v, true) == null) { +// added = true; +// } +// } +// return added; +// } +// +// @Override +// public int hashCode() { +// int h = 0; +// for (K e : this) { +// h += e.hashCode(); +// } +// return h; +// } +// +// @Override +// public boolean equals(Object o) { +// Set c; +// return o instanceof Set && +// ((c = (Set)o) == this || +// containsAll(c) && c.containsAll(this)); +// } +// +// public ConcurrentHashMapSpliterator spliterator() { +// Node[] t; +// ConcurrentHashMapV8 m = this.map; +// long n = m.sumCount(); +// int f = (t = m.table) == null ? 0 : t.length; +// return new KeySpliterator(t, f, 0, f, n < 0L ? 0L : n); +// } +// +// public void forEach(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.map.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// action.apply(p.key); +// } +// } +// } +// } +// +// /** +// * A view of a ConcurrentHashMapV8 as a {@link Collection} of +// * values, in which additions are disabled. This class cannot be +// * directly instantiated. See {@link #values()}. +// */ +// static final class ValuesView extends CollectionView +// implements Collection, java.io.Serializable { +// private static final long serialVersionUID = 2249069246763182397L; +// ValuesView(ConcurrentHashMapV8 map) { super(map); } +// @Override +// public final boolean contains(Object o) { +// return this.map.containsValue(o); +// } +// +// @Override +// public final boolean remove(Object o) { +// if (o != null) { +// for (Iterator it = iterator(); it.hasNext();) { +// if (o.equals(it.next())) { +// it.remove(); +// return true; +// } +// } +// } +// return false; +// } +// +// @Override +// public final Iterator iterator() { +// ConcurrentHashMapV8 m = this.map; +// Node[] t; +// int f = (t = m.table) == null ? 0 : t.length; +// return new ValueIterator(t, f, 0, f, m); +// } +// +// @Override +// public final boolean add(V e) { +// throw new UnsupportedOperationException(); +// } +// @Override +// public final boolean addAll(Collection c) { +// throw new UnsupportedOperationException(); +// } +// +// public ConcurrentHashMapSpliterator spliterator() { +// Node[] t; +// ConcurrentHashMapV8 m = this.map; +// long n = m.sumCount(); +// int f = (t = m.table) == null ? 0 : t.length; +// return new ValueSpliterator(t, f, 0, f, n < 0L ? 0L : n); +// } +// +// public void forEach(Action action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.map.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// action.apply(p.val); +// } +// } +// } +// } +// +// /** +// * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) +// * entries. This class cannot be directly instantiated. See +// * {@link #entrySet()}. +// */ +// static final class EntrySetView extends CollectionView> +// implements Set>, java.io.Serializable { +// private static final long serialVersionUID = 2249069246763182397L; +// EntrySetView(ConcurrentHashMapV8 map) { super(map); } +// +// @Override +// public boolean contains(Object o) { +// Object k, v, r; Map.Entry e; +// return o instanceof Map.Entry && +// (k = (e = (Map.Entry)o).getKey()) != null && +// (r = this.map.getSubscriptions(k)) != null && +// (v = e.getValue()) != null && +// (v == r || v.equals(r)); +// } +// +// @Override +// public boolean remove(Object o) { +// Object k, v; Map.Entry e; +// return o instanceof Map.Entry && +// (k = (e = (Map.Entry)o).getKey()) != null && +// (v = e.getValue()) != null && +// this.map.remove(k, v); +// } +// +// /** +// * @return an iterator over the entries of the backing map +// */ +// @Override +// public Iterator> iterator() { +// ConcurrentHashMapV8 m = this.map; +// Node[] t; +// int f = (t = m.table) == null ? 0 : t.length; +// return new EntryIterator(t, f, 0, f, m); +// } +// +// @Override +// public boolean add(Entry e) { +// return this.map.putVal(e.getKey(), e.getValue(), false) == null; +// } +// +// @Override +// public boolean addAll(Collection> c) { +// boolean added = false; +// for (Entry e : c) { +// if (add(e)) { +// added = true; +// } +// } +// return added; +// } +// +// @Override +// public final int hashCode() { +// int h = 0; +// Node[] t; +// if ((t = this.map.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// h += p.hashCode(); +// } +// } +// return h; +// } +// +// @Override +// public final boolean equals(Object o) { +// Set c; +// return o instanceof Set && +// ((c = (Set)o) == this || +// containsAll(c) && c.containsAll(this)); +// } +// +// public ConcurrentHashMapSpliterator> spliterator() { +// Node[] t; +// ConcurrentHashMapV8 m = this.map; +// long n = m.sumCount(); +// int f = (t = m.table) == null ? 0 : t.length; +// return new EntrySpliterator(t, f, 0, f, n < 0L ? 0L : n, m); +// } +// +// public void forEach(Action> action) { +// if (action == null) { +// throw new NullPointerException(); +// } +// Node[] t; +// if ((t = this.map.table) != null) { +// Traverser it = new Traverser(t, t.length, 0, t.length); +// for (Node p; (p = it.advance()) != null; ) { +// action.apply(new MapEntry(p.key, p.val, this.map)); +// } +// } +// } +// +// } +// +// /* ---------------- Counters -------------- */ +// +// // Adapted from LongAdder and Striped64. +// // See their internal docs for explanation. +// +// // A padded cell for distributing counts +// static final class CounterCell { +// volatile long p0, p1, p2, p3, p4, p5, p6; +// volatile long value; +// volatile long q0, q1, q2, q3, q4, q5, q6; +// CounterCell(long x) { this.value = x; } +// } +// +// /** +// * Holder for the thread-local hash code determining which +// * CounterCell to use. The code is initialized via the +// * counterHashCodeGenerator, but may be moved upon collisions. +// */ +// static final class CounterHashCode { +// int code; +// } +// +// /** +// * Generates initial value for per-thread CounterHashCodes. +// */ +// static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); +// +// /** +// * Increment for counterHashCodeGenerator. See class ThreadLocal +// * for explanation. +// */ +// static final int SEED_INCREMENT = 0x61c88647; +// +// /** +// * Per-thread counter hash codes. Shared across all instances. +// */ +// static final ThreadLocal threadCounterHashCode = +// new ThreadLocal(); +// +// +// final long sumCount() { +// CounterCell[] as = this.counterCells; CounterCell a; +// long sum = this.baseCount; +// if (as != null) { +// for (int i = 0; i < as.length; ++i) { +// if ((a = as[i]) != null) { +// sum += a.value; +// } +// } +// } +// return sum; +// } +// +// // See LongAdder version for explanation +// private final void fullAddCount(long x, CounterHashCode hc, +// boolean wasUncontended) { +// int h; +// if (hc == null) { +// hc = new CounterHashCode(); +// int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); +// h = hc.code = s == 0 ? 1 : s; // Avoid zero +// threadCounterHashCode.set(hc); +// } else { +// h = hc.code; +// } +// boolean collide = false; // True if last slot nonempty +// for (;;) { +// CounterCell[] as; CounterCell a; int n; long v; +// if ((as = this.counterCells) != null && (n = as.length) > 0) { +// if ((a = as[n - 1 & h]) == null) { +// if (this.cellsBusy == 0) { // Try to attach new Cell +// CounterCell r = new CounterCell(x); // Optimistic create +// if (this.cellsBusy == 0 && +// U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { +// boolean created = false; +// try { // Recheck under lock +// CounterCell[] rs; int m, j; +// if ((rs = this.counterCells) != null && +// (m = rs.length) > 0 && +// rs[j = m - 1 & h] == null) { +// rs[j] = r; +// created = true; +// } +// } finally { +// this.cellsBusy = 0; +// } +// if (created) { +// break; +// } +// continue; // Slot is now non-empty +// } +// } +// collide = false; +// } +// else if (!wasUncontended) { +// wasUncontended = true; // Continue after rehash +// } else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) { +// break; +// } else if (this.counterCells != as || n >= NCPU) { +// collide = false; // At max size or stale +// } else if (!collide) { +// collide = true; +// } else if (this.cellsBusy == 0 && +// U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { +// try { +// if (this.counterCells == as) {// Expand table unless stale +// CounterCell[] rs = new CounterCell[n << 1]; +// for (int i = 0; i < n; ++i) { +// rs[i] = as[i]; +// } +// this.counterCells = rs; +// } +// } finally { +// this.cellsBusy = 0; +// } +// collide = false; +// continue; // Retry with expanded table +// } +// h ^= h << 13; // Rehash +// h ^= h >>> 17; +// h ^= h << 5; +// } +// else if (this.cellsBusy == 0 && this.counterCells == as && +// U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { +// boolean init = false; +// try { // Initialize table +// if (this.counterCells == as) { +// CounterCell[] rs = new CounterCell[2]; +// rs[h & 1] = new CounterCell(x); +// this.counterCells = rs; +// init = true; +// } +// } finally { +// this.cellsBusy = 0; +// } +// if (init) { +// break; +// } +// } +// else if (U.compareAndSwapLong(this, BASECOUNT, v = this.baseCount, v + x)) +// { +// break; // Fall back on using base +// } +// } +// hc.code = h; // Record index for next time +// } +// +// // Unsafe mechanics +// private static final sun.misc.Unsafe U; +// private static final long SIZECTL; +// private static final long TRANSFERINDEX; +// private static final long TRANSFERORIGIN; +// private static final long BASECOUNT; +// private static final long CELLSBUSY; +// private static final long CELLVALUE; +// private static final long ABASE; +// private static final int ASHIFT; +// +// static { +// try { +// U = getUnsafe(); +// Class k = ConcurrentHashMapV8.class; +// SIZECTL = U.objectFieldOffset +// (k.getDeclaredField("sizeCtl")); +// TRANSFERINDEX = U.objectFieldOffset +// (k.getDeclaredField("transferIndex")); +// TRANSFERORIGIN = U.objectFieldOffset +// (k.getDeclaredField("transferOrigin")); +// BASECOUNT = U.objectFieldOffset +// (k.getDeclaredField("baseCount")); +// CELLSBUSY = U.objectFieldOffset +// (k.getDeclaredField("cellsBusy")); +// Class ck = CounterCell.class; +// CELLVALUE = U.objectFieldOffset +// (ck.getDeclaredField("value")); +// Class ak = Node[].class; +// ABASE = U.arrayBaseOffset(ak); +// int scale = U.arrayIndexScale(ak); +// if ((scale & scale - 1) != 0) { +// throw new Error("data type scale not a power of two"); +// } +// ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); +// } catch (Exception e) { +// throw new Error(e); +// } +// } +// +// /** +// * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. +// * Replace with a simple call to Unsafe.getUnsafe when integrating +// * into a jdk. +// * +// * @return a sun.misc.Unsafe +// */ +// private static sun.misc.Unsafe getUnsafe() { +// try { +// return sun.misc.Unsafe.getUnsafe(); +// } catch (SecurityException tryReflectionInstead) {} +// try { +// return java.security.AccessController.doPrivileged +// (new java.security.PrivilegedExceptionAction() { +// @Override +// public sun.misc.Unsafe run() throws Exception { +// Class k = sun.misc.Unsafe.class; +// for (java.lang.reflect.Field f : k.getDeclaredFields()) { +// f.setAccessible(true); +// Object x = f.getSubscriptions(null); +// if (k.isInstance(x)) { +// return k.cast(x); +// } +// } +// throw new NoSuchFieldError("the Unsafe"); +// }}); +// } catch (java.security.PrivilegedActionException e) { +// throw new RuntimeException("Could not initialize intrinsics", +// e.getCause()); +// } +// } } diff --git a/src/main/java/dorkbox/util/messagebus/common/HashMapTree.java b/src/main/java/dorkbox/util/messagebus/common/HashMapTree.java index 374a259..c90e308 100644 --- a/src/main/java/dorkbox/util/messagebus/common/HashMapTree.java +++ b/src/main/java/dorkbox/util/messagebus/common/HashMapTree.java @@ -1,15 +1,15 @@ package dorkbox.util.messagebus.common; +import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock; + import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; -import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock; - /** - * Simple tree structure that is a map that contains a chain of keys to get to a value. + * Simple tree structure that is a map that contains a chain of keys to getSubscriptions to a value. *

* THREAD SAFE, each level in the tree has it's own write lock, and there a tree-global read lock, to prevent writes * @@ -506,7 +506,7 @@ public class HashMapTree { READ.lock(); // allows other readers, blocks others from acquiring update or write locks HashMapTree objectTree = null; - // get value from our children + // getSubscriptions value from our children objectTree = getLeaf_NL(key); // protected by lock if (objectTree == null) { @@ -525,7 +525,7 @@ public class HashMapTree { READ.lock(); // allows other readers, blocks others from acquiring update or write locks HashMapTree tree = null; - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(key1); // protected by lock if (tree != null) { tree = tree.getLeaf_NL(key2); // protected by lock @@ -547,7 +547,7 @@ public class HashMapTree { READ.lock(); // allows other readers, blocks others from acquiring update or write locks HashMapTree tree = null; - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(key1); if (tree != null) { tree = tree.getLeaf_NL(key2); @@ -573,7 +573,7 @@ public class HashMapTree { READ.lock(); // allows other readers, blocks others from acquiring update or write locks HashMapTree tree = null; - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(keys[0]); int size = keys.length; @@ -625,7 +625,7 @@ public class HashMapTree { Lock READ = this.lock.readLock(); READ.lock(); // allows other readers, blocks others from acquiring update or write locks - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(key1); if (tree != null) { tree = tree.getLeaf_NL(key2); @@ -642,7 +642,7 @@ public class HashMapTree { Lock READ = this.lock.readLock(); READ.lock(); // allows other readers, blocks others from acquiring update or write locks - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(key1); if (tree != null) { tree = tree.getLeaf_NL(key2); @@ -668,7 +668,7 @@ public class HashMapTree { READ.lock(); // allows other readers, blocks others from acquiring update or write locks HashMapTree tree = null; - // get value from our children + // getSubscriptions value from our children tree = getLeaf_NL(keys[0]); for (int i=1;i { return tree; } -} \ No newline at end of file +} diff --git a/src/main/java/dorkbox/util/messagebus/common/MessageHandler.java b/src/main/java/dorkbox/util/messagebus/common/MessageHandler.java index d9e4d9c..4ca208c 100644 --- a/src/main/java/dorkbox/util/messagebus/common/MessageHandler.java +++ b/src/main/java/dorkbox/util/messagebus/common/MessageHandler.java @@ -1,14 +1,13 @@ package dorkbox.util.messagebus.common; +import com.esotericsoftware.reflectasm.MethodAccess; +import dorkbox.util.messagebus.annotations.Handler; +import dorkbox.util.messagebus.annotations.Synchronized; + import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import com.esotericsoftware.reflectasm.MethodAccess; - -import dorkbox.util.messagebus.annotations.Handler; -import dorkbox.util.messagebus.annotations.Synchronized; - /** * Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains * the handler is called a message listener and more generally, any class containing a message handler in its class hierarchy @@ -30,11 +29,11 @@ import dorkbox.util.messagebus.annotations.Synchronized; */ public class MessageHandler { - // get all listeners defined by the given class (includes + // getSubscriptions all listeners defined by the given class (includes // listeners defined in super classes) public static final MessageHandler[] get(final Class target) { - // get all handlers (this will include all (inherited) methods directly annotated using @Handler) + // getSubscriptions all handlers (this will include all (inherited) methods directly annotated using @Handler) final Method[] allMethods = ReflectionUtils.getMethods(target); final int length = allMethods.length; @@ -97,32 +96,32 @@ public class MessageHandler { this.acceptsVarArgs = handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs(); } - public boolean isSynchronized(){ + public final boolean isSynchronized() { return this.isSynchronized; } - public MethodAccess getHandler() { + public final MethodAccess getHandler() { return this.handler; } - public int getMethodIndex() { + public final int getMethodIndex() { return this.methodIndex; } - public Class[] getHandledMessages() { + public final Class[] getHandledMessages() { return this.handledMessages; } - public boolean acceptsSubtypes() { + public final boolean acceptsSubtypes() { return this.acceptsSubtypes; } - public boolean acceptsVarArgs() { + public final boolean acceptsVarArgs() { return this.acceptsVarArgs; } @Override - public int hashCode() { + public final int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.acceptsSubtypes ? 1231 : 1237); @@ -133,7 +132,7 @@ public class MessageHandler { } @Override - public boolean equals(Object obj) { + public final boolean equals(Object obj) { if (this == obj) { return true; } diff --git a/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSet.java b/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSet.java index 1471818..ee82656 100644 --- a/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSet.java +++ b/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSet.java @@ -84,4 +84,4 @@ public class StrongConcurrentSet extends AbstractConcurrentSet { return this.value; } } -} \ No newline at end of file +} diff --git a/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java b/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java index af7f2bd..43b2398 100644 --- a/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java +++ b/src/main/java/dorkbox/util/messagebus/common/StrongConcurrentSetV8.java @@ -11,11 +11,11 @@ public class StrongConcurrentSetV8 extends StrongConcurrentSet { public StrongConcurrentSetV8(int size, float loadFactor) { // 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks) - super(new ConcurrentHashMapV8>(size, loadFactor, 1)); + super(new ConcurrentHashMapV8>(size, loadFactor, 16)); } public StrongConcurrentSetV8(int size, float loadFactor, int stripeSize) { // 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks) super(new ConcurrentHashMapV8>(size, loadFactor, stripeSize)); } -} \ No newline at end of file +} diff --git a/src/main/java/dorkbox/util/messagebus/common/SuperClassUtils.java b/src/main/java/dorkbox/util/messagebus/common/SuperClassUtils.java new file mode 100644 index 0000000..a9a0324 --- /dev/null +++ b/src/main/java/dorkbox/util/messagebus/common/SuperClassUtils.java @@ -0,0 +1,93 @@ +package dorkbox.util.messagebus.common; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Map; + +public class SuperClassUtils { + + private final Map, Class> versionCache; + private final Map, Class[]> superClassesCache; + + public SuperClassUtils(float loadFactor, int stripeSize) { + this.versionCache = new ConcurrentHashMapV8, Class>(32, loadFactor, stripeSize); + this.superClassesCache = new ConcurrentHashMapV8, Class[]>(32, loadFactor, stripeSize); + } + + /** + * never returns null + * never reset, since it never needs to be reset (as the class hierarchy doesn't change at runtime) + *

+ * if parameter clazz is of type array, then the super classes are of array type as well + *

+ * protected by read lock by caller. The cache version is called first, by write lock + */ + public final Class[] getSuperClasses(final Class clazz, final boolean isArray) { + // this is never reset, since it never needs to be. + final Map, Class[]> local = this.superClassesCache; + + Class[] classes = local.get(clazz); + + if (classes == null) { + // getSubscriptions all super types of class + final Class[] superTypes = ReflectionUtils.getSuperTypes(clazz); + final int length = superTypes.length; + + ArrayList> newList = new ArrayList>(length); + + Class c; + if (isArray) { + for (int i = 0; i < length; i++) { + c = superTypes[i]; + + c = getArrayClass(c); + + if (c != clazz) { + newList.add(c); + } + } + } + else { + for (int i = 0; i < length; i++) { + c = superTypes[i]; + + if (c != clazz) { + newList.add(c); + } + } + } + + classes = newList.toArray(new Class[newList.size()]); + local.put(clazz, classes); + } + + return classes; + } + + /** + * race conditions will result in duplicate answers, which we don't care if happens + * never returns null + * never reset + */ + public final Class getArrayClass(final Class c) { + final Map, Class> versionCache = this.versionCache; + Class clazz = versionCache.get(c); + + if (clazz == null) { + // messy, but the ONLY way to do it. Array super types are also arrays + final Object[] newInstance = (Object[]) Array.newInstance(c, 1); + clazz = newInstance.getClass(); + versionCache.put(c, clazz); + } + + return clazz; + } + + /** + * Clears the caches on shutdown + */ + public final void shutdown() { + this.versionCache.clear(); + this.superClassesCache.clear(); + } +} diff --git a/src/main/java/dorkbox/util/messagebus/common/VarArgUtils.java b/src/main/java/dorkbox/util/messagebus/common/VarArgUtils.java index a020328..fbbd81e 100644 --- a/src/main/java/dorkbox/util/messagebus/common/VarArgUtils.java +++ b/src/main/java/dorkbox/util/messagebus/common/VarArgUtils.java @@ -1,12 +1,13 @@ package dorkbox.util.messagebus.common; -import java.util.ArrayList; -import java.util.Map; -import java.util.concurrent.ConcurrentMap; - import dorkbox.util.messagebus.common.thread.ConcurrentSet; import dorkbox.util.messagebus.common.thread.SubscriptionHolder; import dorkbox.util.messagebus.subscription.Subscription; +import dorkbox.util.messagebus.subscription.SubscriptionUtils; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; public class VarArgUtils { private final ConcurrentMap, ConcurrentSet> varArgSubscriptions; @@ -22,8 +23,8 @@ public class VarArgUtils { private final Map, ArrayList> subscriptionsPerMessageSingle; - public VarArgUtils(SubscriptionUtils utils, Map, ArrayList> subscriptionsPerMessageSingle, - float loadFactor, int stripeSize) { + public VarArgUtils(SubscriptionUtils utils, Map, ArrayList> subscriptionsPerMessageSingle, float loadFactor, + int stripeSize) { this.utils = utils; this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle; @@ -53,7 +54,7 @@ public class VarArgUtils { // // // whenever our subscriptions change, this map is cleared. // SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent; -// ConcurrentSet subsPerType = subHolderConcurrent.get(); +// ConcurrentSet subsPerType = subHolderConcurrent.getSubscriptions(); // // // cache our subscriptions for super classes, so that their access can be fast! // ConcurrentSet putIfAbsent = local.putIfAbsent(messageClass, subsPerType); @@ -67,7 +68,7 @@ public class VarArgUtils { // Iterator iterator; // Subscription sub; // -// Collection subs = this.subscriptionsPerMessageSingle.get(arrayVersion); +// Collection subs = this.subscriptionsPerMessageSingle.getSubscriptions(arrayVersion); // if (subs != null) { // for (iterator = subs.iterator(); iterator.hasNext();) { // sub = iterator.next(); @@ -95,7 +96,7 @@ public class VarArgUtils { // ConcurrentMap, ConcurrentSet> local = this.varArgSuperClassSubscriptions; // // SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent; -// ConcurrentSet subsPerType = subHolderConcurrent.get(); +// ConcurrentSet subsPerType = subHolderConcurrent.getSubscriptions(); // // // cache our subscriptions for super classes, so that their access can be fast! // ConcurrentSet putIfAbsent = local.putIfAbsent(messageClass, subsPerType); @@ -122,7 +123,7 @@ public class VarArgUtils { // for (iterator = types.iterator(); iterator.hasNext();) { // superClass = iterator.next(); // -// Collection subs = local2.get(superClass); +// Collection subs = local2.getSubscriptions(superClass); // if (subs != null) { // for (subIterator = subs.iterator(); subIterator.hasNext();) { // sub = subIterator.next(); @@ -158,7 +159,7 @@ public class VarArgUtils { // subsPerType = subsPerTypeLeaf.getValue(); // } else { // SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent; -// subsPerType = subHolderConcurrent.get(); +// subsPerType = subHolderConcurrent.getSubscriptions(); // // ConcurrentSet putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2); // if (putIfAbsent != null) { @@ -189,10 +190,11 @@ public class VarArgUtils { } - // CAN NOT RETURN NULL + // CAN NOT RETURN NULL // check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI // and then, returns the array'd version subscriptions - public ConcurrentSet getVarArgSuperSubscriptions(final Class messageClass1, final Class messageClass2, final Class messageClass3) { + public ConcurrentSet getVarArgSuperSubscriptions(final Class messageClass1, final Class messageClass2, + final Class messageClass3) { // HashMapTree, ConcurrentSet> local = this.varArgSuperClassSubscriptionsMulti; // // // whenever our subscriptions change, this map is cleared. @@ -205,7 +207,7 @@ public class VarArgUtils { // subsPerType = subsPerTypeLeaf.getValue(); // } else { // SubscriptionHolder subHolderConcurrent = this.subHolderConcurrent; -// subsPerType = subHolderConcurrent.get(); +// subsPerType = subHolderConcurrent.getSubscriptions(); // // ConcurrentSet putIfAbsent = local.putIfAbsent(subsPerType, messageClass1, messageClass2, messageClass3); // if (putIfAbsent != null) { diff --git a/src/main/java/dorkbox/util/messagebus/common/WeakConcurrentSet.java b/src/main/java/dorkbox/util/messagebus/common/WeakConcurrentSet.java index 6d0585a..6db19ef 100644 --- a/src/main/java/dorkbox/util/messagebus/common/WeakConcurrentSet.java +++ b/src/main/java/dorkbox/util/messagebus/common/WeakConcurrentSet.java @@ -3,8 +3,7 @@ package dorkbox.util.messagebus.common; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.WeakHashMap; - -import dorkbox.util.messagebus.common.thread.StampedLock; +import java.util.concurrent.locks.StampedLock; /** * This implementation uses weak references to the elements. Iterators automatically perform cleanups of @@ -38,6 +37,8 @@ public class WeakConcurrentSet extends AbstractConcurrentSet{ StampedLock lock = WeakConcurrentSet.this.lock; long stamp = lock.writeLock(); +// final Lock writeLock = WeakConcurrentSet.this.lock.writeLock(); +// writeLock.lock(); try{ do { ISetEntry orphaned = this.current; @@ -47,6 +48,7 @@ public class WeakConcurrentSet extends AbstractConcurrentSet{ } finally { lock.unlockWrite(stamp); +// writeLock.unlock(); } } @@ -118,9 +120,5 @@ public class WeakConcurrentSet extends AbstractConcurrentSet{ public T getValue() { return this.value.get(); } - - - - } } diff --git a/src/main/java/dorkbox/util/messagebus/common/item3.java b/src/main/java/dorkbox/util/messagebus/common/item3.java new file mode 100644 index 0000000..4fb286b --- /dev/null +++ b/src/main/java/dorkbox/util/messagebus/common/item3.java @@ -0,0 +1,11 @@ +package dorkbox.util.messagebus.common; + +import java.util.Iterator; + +abstract class item3 implements Iterator { + public ISetEntry current; + + public item3(ISetEntry current) { + this.current = current; + } +} diff --git a/src/main/java/dorkbox/util/messagebus/common/simpleq/MpmcMultiTransferArrayQueue.java b/src/main/java/dorkbox/util/messagebus/common/simpleq/MpmcMultiTransferArrayQueue.java index cff04ec..795da92 100644 --- a/src/main/java/dorkbox/util/messagebus/common/simpleq/MpmcMultiTransferArrayQueue.java +++ b/src/main/java/dorkbox/util/messagebus/common/simpleq/MpmcMultiTransferArrayQueue.java @@ -1,25 +1,12 @@ package dorkbox.util.messagebus.common.simpleq; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem1; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem2; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpItem3; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpThread; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lpType; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lvMessageType; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.lvThread; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.soThread; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem1; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem2; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spItem3; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spMessageType; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spThread; -import static dorkbox.util.messagebus.common.simpleq.MultiNode.spType; - -import java.util.concurrent.ThreadLocalRandom; - import org.jctools.queues.MpmcArrayQueue; import org.jctools.util.UnsafeAccess; +import java.util.concurrent.ThreadLocalRandom; + +import static dorkbox.util.messagebus.common.simpleq.MultiNode.*; + public final class MpmcMultiTransferArrayQueue extends MpmcArrayQueue { private static final int TYPE_EMPTY = 0; @@ -445,7 +432,7 @@ public final class MpmcMultiTransferArrayQueue extends MpmcArrayQueue { // Successful CAS: full barrier final Thread myThread = Thread.currentThread(); -// final Object node = nodeThreadLocal.get(); +// final Object node = nodeThreadLocal.getSubscriptions(); spType(node, TYPE_CONSUMER); spThread(node, myThread); diff --git a/src/main/java/dorkbox/util/messagebus/common/thread/ConcurrentSet.java b/src/main/java/dorkbox/util/messagebus/common/thread/ConcurrentSet.java index cbc7b88..94eec3a 100644 --- a/src/main/java/dorkbox/util/messagebus/common/thread/ConcurrentSet.java +++ b/src/main/java/dorkbox/util/messagebus/common/thread/ConcurrentSet.java @@ -1,13 +1,13 @@ package dorkbox.util.messagebus.common.thread; +import dorkbox.util.messagebus.common.ConcurrentHashMapV8; + import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; -import dorkbox.util.messagebus.common.ConcurrentHashMapV8; - /** * This data structure is optimized for non-blocking reads even when write operations occur. * Running read iterators will not be affected by add operations since writes always insert at the head of the @@ -39,7 +39,7 @@ public class ConcurrentSet extends ConcurrentLinkedQueue2 { return false; } - // had to modify the super implementation so we get Node back + // had to modify the super implementation so we getSubscriptions Node back Node alreadyPresent = this.entries.putIfAbsent(element, this.IN_PROGRESS_MARKER); if (alreadyPresent == null) { // this doesn't already exist diff --git a/src/main/java/dorkbox/util/messagebus/subscription/Matcher.java b/src/main/java/dorkbox/util/messagebus/subscription/Matcher.java new file mode 100644 index 0000000..4420a15 --- /dev/null +++ b/src/main/java/dorkbox/util/messagebus/subscription/Matcher.java @@ -0,0 +1,5 @@ +package dorkbox.util.messagebus.subscription; + +public interface Matcher { + Subscription[] getSubscriptions(Class messageClass); +} diff --git a/src/main/java/dorkbox/util/messagebus/subscription/Subscription.java b/src/main/java/dorkbox/util/messagebus/subscription/Subscription.java index b9df30d..cdd1be3 100644 --- a/src/main/java/dorkbox/util/messagebus/subscription/Subscription.java +++ b/src/main/java/dorkbox/util/messagebus/subscription/Subscription.java @@ -1,19 +1,17 @@ package dorkbox.util.messagebus.subscription; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.atomic.AtomicInteger; - -import org.omg.CORBA.BooleanHolder; - import com.esotericsoftware.reflectasm.MethodAccess; - import dorkbox.util.messagebus.common.MessageHandler; import dorkbox.util.messagebus.common.StrongConcurrentSetV8; import dorkbox.util.messagebus.dispatch.IHandlerInvocation; import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation; import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation; import dorkbox.util.messagebus.error.ErrorHandlingSupport; +import org.omg.CORBA.BooleanHolder; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; /** * A subscription is a thread-safe container that manages exactly one message handler of all registered @@ -43,9 +41,10 @@ public class Subscription { public Subscription(MessageHandler handler) { this.handlerMetadata = handler; - this.listeners = new StrongConcurrentSetV8(16, 0.85F, 16); + this.listeners = new StrongConcurrentSetV8(16, 0.85F, 15); // this.listeners = new StrongConcurrentSet(16, 0.85F); // this.listeners = new ConcurrentLinkedQueue2(); +// this.listeners = new CopyOnWriteArrayList(); IHandlerInvocation invocation = new ReflectiveHandlerInvocation(); if (handler.isSynchronized()) { @@ -59,26 +58,26 @@ public class Subscription { return this.handlerMetadata.getHandledMessages(); } - public boolean acceptsSubtypes() { + public final boolean acceptsSubtypes() { return this.handlerMetadata.acceptsSubtypes(); } - public boolean acceptsVarArgs() { + public final boolean acceptsVarArgs() { return this.handlerMetadata.acceptsVarArgs(); } - public boolean isEmpty() { + public final boolean isEmpty() { return this.listeners.isEmpty(); } - public void subscribe(Object listener) { + public final void subscribe(Object listener) { this.listeners.add(listener); } /** * @return TRUE if the element was removed */ - public boolean unsubscribe(Object existingListener) { + public final boolean unsubscribe(Object existingListener) { return this.listeners.remove(existingListener); } @@ -96,9 +95,9 @@ public class Subscription { * @return true if there were listeners for this publication, false if there was nothing */ public final void publish(final Object message) throws Throwable { - MethodAccess handler = this.handlerMetadata.getHandler(); - int handleIndex = this.handlerMetadata.getMethodIndex(); - IHandlerInvocation invocation = this.invocation; + final MethodAccess handler = this.handlerMetadata.getHandler(); + final int handleIndex = this.handlerMetadata.getMethodIndex(); + final IHandlerInvocation invocation = this.invocation; Iterator iterator; Object listener; @@ -107,7 +106,6 @@ public class Subscription { listener = iterator.next(); // this.c++; - invocation.invoke(listener, handler, handleIndex, message); } } diff --git a/src/main/java/dorkbox/util/messagebus/SubscriptionManager.java b/src/main/java/dorkbox/util/messagebus/subscription/SubscriptionManager.java similarity index 82% rename from src/main/java/dorkbox/util/messagebus/SubscriptionManager.java rename to src/main/java/dorkbox/util/messagebus/subscription/SubscriptionManager.java index a290452..9435af4 100644 --- a/src/main/java/dorkbox/util/messagebus/SubscriptionManager.java +++ b/src/main/java/dorkbox/util/messagebus/subscription/SubscriptionManager.java @@ -1,28 +1,25 @@ -package dorkbox.util.messagebus; +package dorkbox.util.messagebus.subscription; import dorkbox.util.messagebus.common.*; import dorkbox.util.messagebus.common.thread.ConcurrentSet; -import dorkbox.util.messagebus.subscription.Subscription; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.StampedLock; /** * The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process. * It provides fast lookup of existing subscriptions when another instance of an already known * listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines * message handlers. - * - * + *

+ *

* Subscribe/Unsubscribe, while it is possible for them to be 100% concurrent (in relation to listeners per subscription), * getting an accurate reflection of the number of subscriptions, or guaranteeing a "HAPPENS-BEFORE" relationship really * complicates this, so it has been modified for subscribe/unsubscibe to be mutually exclusive. - * + *

* Given these restrictions and complexity, it is much easier to create a MPSC blocking queue, and have a single thread * manage sub/unsub. * @@ -58,24 +55,25 @@ public class SubscriptionManager { private final VarArgUtils varArgUtils; -// private final StampedLock lock = new StampedLock(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final StampedLock lock = new StampedLock(); +// private final ReadWriteLock lock = new ReentrantReadWriteLock(); - SubscriptionManager(int numberOfThreads) { + public SubscriptionManager(int numberOfThreads) { float loadFactor = SubscriptionManager.LOAD_FACTOR; // modified ONLY during SUB/UNSUB { this.nonListeners = new ConcurrentHashMapV8, Boolean>(4, loadFactor, numberOfThreads); - this.subscriptionsPerMessageSingle = new ConcurrentHashMapV8, ArrayList>(64, LOAD_FACTOR, 1); + this.subscriptionsPerMessageSingle = new ConcurrentHashMapV8, ArrayList>(32, LOAD_FACTOR, 1); this.subscriptionsPerMessageMulti = new HashMapTree, ArrayList>(4, loadFactor); // only used during SUB/UNSUB this.subscriptionsPerListener = new ConcurrentHashMapV8, Subscription[]>(); } - this.utils = new SubscriptionUtils(this.subscriptionsPerMessageSingle, this.subscriptionsPerMessageMulti, loadFactor, numberOfThreads); + this.utils = new SubscriptionUtils(this.subscriptionsPerMessageSingle, this.subscriptionsPerMessageMulti, loadFactor, + numberOfThreads); // var arg subscriptions keep track of which subscriptions can handle varArgs. SUB/UNSUB dumps it, so it is recreated dynamically. // it's a hit on SUB/UNSUB, but improves performance of handlers @@ -118,8 +116,11 @@ public class SubscriptionManager { if (subscriptions == null) { // now write lock for the least expensive part. This is a deferred "double checked lock", but is necessary because // of the huge number of reads compared to writes. - Lock writeLock = this.lock.writeLock(); - writeLock.lock(); + + StampedLock lock = this.lock; + long stamp = lock.writeLock(); +// Lock writeLock = this.lock.writeLock(); +// writeLock.lock(); ConcurrentMap, Subscription[]> subsPerListener2 = this.subscriptionsPerListener; subscriptions = subsPerListener2.get(listenerClass); @@ -132,7 +133,8 @@ public class SubscriptionManager { // remember the class as non listening class if no handlers are found if (handlersSize == 0) { this.nonListeners.put(listenerClass, Boolean.TRUE); - writeLock.unlock(); + lock.unlockWrite(stamp); +// writeLock.unlock(); return; } @@ -147,7 +149,7 @@ public class SubscriptionManager { // create the subscription MessageHandler messageHandler; - for (int i=0;i getSubsForPublication(final Class[] messageHandlerTypes, - final Map, ArrayList> subsPerMessageSingle, - final HashMapTree, ArrayList> subsPerMessageMulti, - final VarArgPossibility varArgPossibility) { + final Map, ArrayList> subsPerMessageSingle, + final HashMapTree, ArrayList> subsPerMessageMulti, + final VarArgPossibility varArgPossibility) { final int size = messageHandlerTypes.length; @@ -200,15 +206,13 @@ public class SubscriptionManager { if (subs == null) { subs = new ArrayList(); - boolean isArray = utils.isArray(type0); + boolean isArray = type0.isArray(); if (isArray) { varArgPossibility.set(true); } // cache the super classes -// todo: makes it's own read/write lock. it's 2x as expensive when running inside the writelock for subscribe, VS on it's own -// maybe even use StampedLock - utils.cacheSuperClasses(type0, isArray); +// utils.cacheSuperClasses(type0, isArray); subsPerMessageSingle.put(type0, subs); } @@ -218,7 +222,7 @@ public class SubscriptionManager { case 2: { // the HashMapTree uses read/write locks, so it is only accessible one thread at a time // SubscriptionHolder subHolderSingle = this.subHolderSingle; -// subsPerType = subHolderSingle.get(); +// subsPerType = subHolderSingle.getSubscriptions(); // // Collection putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1]); // if (putIfAbsent != null) { @@ -236,7 +240,7 @@ public class SubscriptionManager { case 3: { // the HashMapTree uses read/write locks, so it is only accessible one thread at a time // SubscriptionHolder subHolderSingle = this.subHolderSingle; -// subsPerType = subHolderSingle.get(); +// subsPerType = subHolderSingle.getSubscriptions(); // // Collection putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, type0, types[1], types[2]); // if (putIfAbsent != null) { @@ -255,7 +259,7 @@ public class SubscriptionManager { default: { // the HashMapTree uses read/write locks, so it is only accessible one thread at a time // SubscriptionHolder subHolderSingle = this.subHolderSingle; -// subsPerType = subHolderSingle.get(); +// subsPerType = subHolderSingle.getSubscriptions(); // // Collection putIfAbsent = subsPerMessageMulti.putIfAbsent(subsPerType, types); // if (putIfAbsent != null) { @@ -297,7 +301,7 @@ public class SubscriptionManager { if (subscriptions != null) { Subscription subscription; - for (int i=0;i listenerClass) { + private Subscription[] getListenerSubs(Class listenerClass) { Subscription[] subscriptions; - Lock readLock = this.lock.readLock(); - readLock.lock(); + StampedLock lock = this.lock; + long stamp = lock.readLock(); +// Lock readLock = this.lock.readLock(); +// readLock.lock(); + subscriptions = this.subscriptionsPerListener.get(listenerClass); - readLock.unlock(); + + lock.unlockRead(stamp); +// readLock.unlock(); return subscriptions; } @@ -326,46 +335,59 @@ public class SubscriptionManager { ArrayList collection; Subscription[] subscriptions; - Lock readLock = this.lock.readLock(); - readLock.lock(); + StampedLock lock = this.lock; + long stamp = lock.readLock(); +// Lock readLock = this.lock.readLock(); +// readLock.lock(); collection = this.subscriptionsPerMessageSingle.get(messageClass); - +// if (collection != null) { subscriptions = collection.toArray(new Subscription[collection.size()]); - } else { - subscriptions = EMPTY; + } + else { +// subscriptions = EMPTY; + subscriptions = null; } - readLock.unlock(); + lock.unlockRead(stamp); +// readLock.unlock(); return subscriptions; } // never return null public final Subscription[] getSubscriptions(final Class messageClass) { ArrayList collection; - Subscription[] subscriptions = null; - - Lock readLock = this.lock.readLock(); - readLock.lock(); - collection = this.subscriptionsPerMessageSingle.get(messageClass); + StampedLock lock = this.lock; + long stamp = lock.readLock(); +// Lock readLock = this.lock.readLock(); +// readLock.lock(); + + collection = this.subscriptionsPerMessageSingle.get(messageClass); // can return null + + // now getSubscriptions superClasses + ArrayList superSubscriptions = this.utils.getSuperSubscriptions(messageClass); // NOT return null if (collection != null) { collection = new ArrayList(collection); - - // now get superClasses - ArrayList superSubscriptions = this.utils.getSuperSubscriptions(messageClass); // NOT return null collection.addAll(superSubscriptions); - } else { - - // now get superClasses - collection = this.utils.getSuperSubscriptions(messageClass); // NOT return null + } + else { + collection = superSubscriptions; } - subscriptions = collection.toArray(new Subscription[collection.size()]); - readLock.unlock(); + final Subscription[] subscriptions; + if (collection != null) { + subscriptions = collection.toArray(new Subscription[collection.size()]); + } + else { + subscriptions = null; + } + + lock.unlockRead(stamp); +// readLock.unlock(); return subscriptions; } @@ -381,7 +403,7 @@ public class SubscriptionManager { // readLock.lock(); // // try { -// collection = this.subscriptionsPerMessageSingle.get(messageType); +// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType); // if (collection != null) { // subscriptions = collection.toArray(EMPTY); // } @@ -394,7 +416,7 @@ public class SubscriptionManager { // //// long stamp = this.lock.tryOptimisticRead(); // non blocking //// -//// collection = this.subscriptionsPerMessageSingle.get(messageType); +//// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType); //// if (collection != null) { ////// subscriptions = new ArrayDeque<>(collection); //// subscriptions = new ArrayList<>(collection); @@ -407,7 +429,7 @@ public class SubscriptionManager { //// if (!this.lock.validate(stamp)) { // if a write occurred, try again with a read lock //// stamp = this.lock.readLock(); //// try { -//// collection = this.subscriptionsPerMessageSingle.get(messageType); +//// collection = this.subscriptionsPerMessageSingle.getSubscriptions(messageType); //// if (collection != null) { ////// subscriptions = new ArrayDeque<>(collection); //// subscriptions = new ArrayList<>(collection); @@ -448,7 +470,8 @@ public class SubscriptionManager { // CAN RETURN NULL - public final Collection getSubscriptionsByMessageType(Class messageType1, Class messageType2, Class messageType3) { + public final Collection getSubscriptionsByMessageType(Class messageType1, Class messageType2, + Class messageType3) { return this.subscriptionsPerMessageMulti.getValue(messageType1, messageType2, messageType3); } @@ -481,7 +504,8 @@ public class SubscriptionManager { // CAN NOT RETURN NULL // check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI // and then, returns the array'd version subscriptions - public Collection getVarArgSuperSubscriptions(final Class messageClass1, final Class messageClass2, final Class messageClass3) { + public Collection getVarArgSuperSubscriptions(final Class messageClass1, final Class messageClass2, + final Class messageClass3) { return this.varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3); } diff --git a/src/main/java/dorkbox/util/messagebus/common/SubscriptionUtils.java b/src/main/java/dorkbox/util/messagebus/subscription/SubscriptionUtils.java similarity index 72% rename from src/main/java/dorkbox/util/messagebus/common/SubscriptionUtils.java rename to src/main/java/dorkbox/util/messagebus/subscription/SubscriptionUtils.java index 1eda3ea..5cd9696 100644 --- a/src/main/java/dorkbox/util/messagebus/common/SubscriptionUtils.java +++ b/src/main/java/dorkbox/util/messagebus/subscription/SubscriptionUtils.java @@ -1,33 +1,24 @@ -package dorkbox.util.messagebus.common; +package dorkbox.util.messagebus.subscription; + +import dorkbox.util.messagebus.common.ConcurrentHashMapV8; +import dorkbox.util.messagebus.common.HashMapTree; +import dorkbox.util.messagebus.common.SuperClassUtils; +import dorkbox.util.messagebus.common.thread.ClassHolder; +import dorkbox.util.messagebus.common.thread.ConcurrentSet; +import dorkbox.util.messagebus.common.thread.SubscriptionHolder; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Map; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import dorkbox.util.messagebus.common.thread.ClassHolder; -import dorkbox.util.messagebus.common.thread.ConcurrentSet; -import dorkbox.util.messagebus.common.thread.StampedLock; -import dorkbox.util.messagebus.common.thread.SubscriptionHolder; -import dorkbox.util.messagebus.subscription.Subscription; public class SubscriptionUtils { - private static final Class[] SUPER_CLASS_EMPTY = new Class[0]; + private final SuperClassUtils superClass; - private StampedLock superClassLock = new StampedLock(); - - - private final Map, Class> arrayVersionCache; - private final Map, Boolean> isArrayCache; - - private final Map, Class[]> superClassesCache; private final ClassHolder classHolderSingle; // superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically. // it's a hit on SUB/UNSUB, but REALLY improves performance on handlers - // it's faster to create a new one for SUB/UNSUB than it is to clear() on the original one + // it's faster to create a new one for SUB/UNSUB than it is to shutdown() on the original one private final Map, ArrayList> superClassSubscriptions; private final HashMapTree, ConcurrentSet> superClassSubscriptionsMulti; @@ -40,16 +31,15 @@ public class SubscriptionUtils { public SubscriptionUtils(Map, ArrayList> subscriptionsPerMessageSingle, - HashMapTree, ArrayList> subscriptionsPerMessageMulti, - float loadFactor, int stripeSize) { + HashMapTree, ArrayList> subscriptionsPerMessageMulti, float loadFactor, + int stripeSize) { + + this.superClass = new SuperClassUtils(loadFactor, 1); this.subscriptionsPerMessageSingle = subscriptionsPerMessageSingle; this.subscriptionsPerMessageMulti = subscriptionsPerMessageMulti; - this.arrayVersionCache = new ConcurrentHashMapV8, Class>(32, loadFactor, stripeSize); - this.isArrayCache = new ConcurrentHashMapV8, Boolean>(32, loadFactor, stripeSize); - this.superClassesCache = new ConcurrentHashMapV8, Class[]>(32, loadFactor, 8); this.classHolderSingle = new ClassHolder(loadFactor, stripeSize); // superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically. @@ -65,66 +55,15 @@ public class SubscriptionUtils { this.superClassSubscriptions.clear(); } - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - /** - * never returns null - * never reset, since it never needs to be reset (as the class hierarchy doesn't change at runtime) - * - * if parameter clazz is of type array, then the super classes are of array type as well - * - * protected by read lock by caller. The cache version is called first, by write lock - */ - public final Class[] getSuperClasses_NL(final Class clazz, final boolean isArray) { - // this is never reset, since it never needs to be. - final Map, Class[]> local = this.superClassesCache; - - Class[] classes = local.get(clazz); - - if (classes == null) { - // get all super types of class - final Class[] superTypes = ReflectionUtils.getSuperTypes(clazz); - int length = superTypes.length; - - ArrayList> newList = new ArrayList>(length); - - Class c; - if (isArray) { - for (int i=0;i[newList.size()]); - local.put(clazz, classes); - } - - return classes; - } // called inside sub/unsub write lock public final void cacheSuperClasses(final Class clazz) { - getSuperClasses_NL(clazz, isArray(clazz)); + this.superClass.getSuperClasses(clazz, clazz.isArray()); } // called inside sub/unsub write lock public final void cacheSuperClasses(final Class clazz, final boolean isArray) { - getSuperClasses_NL(clazz, isArray); + this.superClass.getSuperClasses(clazz, isArray); } // public final Class[] getSuperClasses(Class clazz, boolean isArray) { @@ -136,7 +75,7 @@ public class SubscriptionUtils { // long stamp = lock.tryOptimisticRead(); // // if (stamp > 0) { -// ArrayList> arrayList = local.get(clazz); +// ArrayList> arrayList = local.getSubscriptions(clazz); // if (arrayList != null) { // classes = arrayList.toArray(SUPER_CLASS_EMPTY); // @@ -145,7 +84,7 @@ public class SubscriptionUtils { // } else { // stamp = lock.readLock(); // -// arrayList = local.get(clazz); +// arrayList = local.getSubscriptions(clazz); // if (arrayList != null) { // classes = arrayList.toArray(SUPER_CLASS_EMPTY); // lock.unlockRead(stamp); @@ -155,7 +94,7 @@ public class SubscriptionUtils { // } // } // -// // unable to get a valid subscription. Have to acquire a write lock +// // unable to getSubscriptions a valid subscription. Have to acquire a write lock // long origStamp = stamp; // if ((stamp = lock.tryConvertToWriteLock(stamp)) == 0) { // lock.unlockRead(origStamp); @@ -163,7 +102,7 @@ public class SubscriptionUtils { // } // // -// // get all super types of class +// // getSubscriptions all super types of class // Collection> superTypes = ReflectionUtils.getSuperTypes(clazz); // ArrayList> arrayList = new ArrayList>(superTypes.size()); // Iterator> iterator; @@ -189,89 +128,51 @@ public class SubscriptionUtils { // return classes; // } - /** - * race conditions will result in duplicate answers, which we don't care if happens - * never returns null - * never reset - */ - public final Class getArrayClass(final Class c) { - final Map, Class> arrayVersionCache = this.arrayVersionCache; - Class clazz = arrayVersionCache.get(c); - - if (clazz == null) { - // messy, but the ONLY way to do it. Array super types are also arrays - final Object[] newInstance = (Object[]) Array.newInstance(c, 1); - clazz = newInstance.getClass(); - arrayVersionCache.put(c, clazz); - } - - return clazz; - } - - /** - * Cache the values of JNI method, isArray(c) - * @return true if the class c is an array type - */ -// @SuppressWarnings("boxing") - public final boolean isArray(final Class c) { -// final Map, Boolean> isArrayCache = this.isArrayCache; -// -// final Boolean isArray = isArrayCache.get(c); -// if (isArray == null) { - boolean b = c.isArray(); -// isArrayCache.put(c, b); - return b; -// } -// return isArray; - } public void shutdown() { - this.isArrayCache.clear(); - this.arrayVersionCache.clear(); - this.superClassesCache.clear(); + this.superClass.shutdown(); } - private static Subscription[] EMPTY = new Subscription[0]; - private static Class[] EMPTY2 = new Class[0]; - - private StampedLock superSubLock = new StampedLock(); - /** * Returns an array COPY of the super subscriptions for the specified type. - * + *

* This ALSO checks to see if the superClass accepts subtypes. - * + *

* protected by read lock by caller * * @return CAN NOT RETURN NULL */ - public final ArrayList getSuperSubscriptions(final Class superType) { + public final ArrayList getSuperSubscriptions(final Class clazz) { // whenever our subscriptions change, this map is cleared. final Map, ArrayList> local = this.superClassSubscriptions; - ArrayList superSubscriptions = local.get(superType); + ArrayList superSubscriptions = local.get(clazz); + if (superSubscriptions == null) { - final Class[] superClasses = getSuperClasses_NL(superType, isArray(superType)); // never returns null, cached response - final int length = superClasses.length; - - // types was not empty, so get subscriptions for each type and collate them + // types was not empty, so getSubscriptions subscriptions for each type and collate them final Map, ArrayList> local2 = this.subscriptionsPerMessageSingle; - Class superClass; - ArrayList subs; + // save the subscriptions + final Class[] superClasses = this.superClass.getSuperClasses(clazz, clazz.isArray()); // never returns null, cached response + + Class superClass; + ArrayList superSubs; Subscription sub; + final int length = superClasses.length; + int superSubLengh; superSubscriptions = new ArrayList(length); - for (int i=0;i putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2); @@ -387,7 +288,7 @@ public class SubscriptionUtils { // subsPerType = subsPerTypeLeaf.getValue(); // } else { // SubscriptionHolder subHolderSingle = this.subHolderSingle; -// subsPerType = subHolderSingle.get(); +// subsPerType = subHolderSingle.getSubscriptions(); // // // cache our subscriptions for super classes, so that their access can be fast! // ConcurrentSet putIfAbsent = local.putIfAbsent(subsPerType, superType1, superType2, superType3); diff --git a/src/test/java/dorkbox/util/messagebus/SubscriptionManagerTest.java b/src/test/java/dorkbox/util/messagebus/SubscriptionManagerTest.java index fcdcd5e..28115f9 100644 --- a/src/test/java/dorkbox/util/messagebus/SubscriptionManagerTest.java +++ b/src/test/java/dorkbox/util/messagebus/SubscriptionManagerTest.java @@ -1,30 +1,12 @@ package dorkbox.util.messagebus; +import dorkbox.util.messagebus.common.*; +import dorkbox.util.messagebus.listeners.*; +import dorkbox.util.messagebus.messages.*; +import dorkbox.util.messagebus.subscription.SubscriptionManager; import org.junit.Test; -import dorkbox.util.messagebus.common.AssertSupport; -import dorkbox.util.messagebus.common.ConcurrentExecutor; -import dorkbox.util.messagebus.common.ListenerFactory; -import dorkbox.util.messagebus.common.SubscriptionValidator; -import dorkbox.util.messagebus.common.TestUtil; -import dorkbox.util.messagebus.listeners.AbstractMessageListener; -import dorkbox.util.messagebus.listeners.ICountableListener; -import dorkbox.util.messagebus.listeners.IMessageListener; -import dorkbox.util.messagebus.listeners.IMultipartMessageListener; -import dorkbox.util.messagebus.listeners.MessagesListener; -import dorkbox.util.messagebus.listeners.MultipartMessageListener; -import dorkbox.util.messagebus.listeners.Overloading; -import dorkbox.util.messagebus.listeners.StandardMessageListener; -import dorkbox.util.messagebus.messages.AbstractMessage; -import dorkbox.util.messagebus.messages.ICountable; -import dorkbox.util.messagebus.messages.IMessage; -import dorkbox.util.messagebus.messages.IMultipartMessage; -import dorkbox.util.messagebus.messages.MessageTypes; -import dorkbox.util.messagebus.messages.MultipartMessage; -import dorkbox.util.messagebus.messages.StandardMessage; - /** - * * Test the subscriptions as generated and organized by the subscription manager. Tests use different sets of listeners * and corresponding expected set of subscriptions that should result from subscribing the listeners. The subscriptions * are tested for the type of messages they should handle and @@ -36,149 +18,124 @@ public class SubscriptionManagerTest extends AssertSupport { private static final int InstancesPerListener = 5000; - @Test - public void testIMessageListener(){ - ListenerFactory listeners = listeners( - IMessageListener.DefaultListener.class, - IMessageListener.DisabledListener.class, - IMessageListener.NoSubtypesListener.class); + @Test public void testIMessageListener() { + ListenerFactory listeners = listeners(IMessageListener.DefaultListener.class, IMessageListener.DisabledListener.class, + IMessageListener.NoSubtypesListener.class); - SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(IMessageListener.DefaultListener.class).handles(IMessage.class, - AbstractMessage.class, IMultipartMessage.class, StandardMessage.class, MessageTypes.class) - .listener(IMessageListener.NoSubtypesListener.class).handles(IMessage.class); + SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners).listener(IMessageListener.DefaultListener.class) + .handles(IMessage.class, AbstractMessage.class, IMultipartMessage.class, StandardMessage.class, MessageTypes.class) + .listener(IMessageListener.NoSubtypesListener.class).handles(IMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testAbstractMessageListener(){ - ListenerFactory listeners = listeners( - AbstractMessageListener.DefaultListener.class, - AbstractMessageListener.DisabledListener.class, - AbstractMessageListener.NoSubtypesListener.class); + @Test public void testAbstractMessageListener() { + ListenerFactory listeners = listeners(AbstractMessageListener.DefaultListener.class, AbstractMessageListener.DisabledListener.class, + AbstractMessageListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(AbstractMessageListener.NoSubtypesListener.class).handles(AbstractMessage.class) - .listener(AbstractMessageListener.DefaultListener.class).handles(StandardMessage.class, AbstractMessage.class); + .listener(AbstractMessageListener.NoSubtypesListener.class).handles(AbstractMessage.class) + .listener(AbstractMessageListener.DefaultListener.class).handles(StandardMessage.class, AbstractMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testMessagesListener(){ - ListenerFactory listeners = listeners( - MessagesListener.DefaultListener.class, - MessagesListener.DisabledListener.class, - MessagesListener.NoSubtypesListener.class); + @Test public void testMessagesListener() { + ListenerFactory listeners = listeners(MessagesListener.DefaultListener.class, MessagesListener.DisabledListener.class, + MessagesListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(MessagesListener.NoSubtypesListener.class).handles(MessageTypes.class) - .listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class); + .listener(MessagesListener.NoSubtypesListener.class).handles(MessageTypes.class) + .listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testMultipartMessageListener(){ - ListenerFactory listeners = listeners( - MultipartMessageListener.DefaultListener.class, - MultipartMessageListener.DisabledListener.class, - MultipartMessageListener.NoSubtypesListener.class); + @Test public void testMultipartMessageListener() { + ListenerFactory listeners = listeners(MultipartMessageListener.DefaultListener.class, + MultipartMessageListener.DisabledListener.class, + MultipartMessageListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(MultipartMessageListener.NoSubtypesListener.class).handles(MultipartMessage.class) - .listener(MultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class); + .listener(MultipartMessageListener.NoSubtypesListener.class).handles(MultipartMessage.class) + .listener(MultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testIMultipartMessageListener(){ - ListenerFactory listeners = listeners( - IMultipartMessageListener.DefaultListener.class, - IMultipartMessageListener.DisabledListener.class, - IMultipartMessageListener.NoSubtypesListener.class); + @Test public void testIMultipartMessageListener() { + ListenerFactory listeners = listeners(IMultipartMessageListener.DefaultListener.class, + IMultipartMessageListener.DisabledListener.class, + IMultipartMessageListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(IMultipartMessageListener.NoSubtypesListener.class).handles(IMultipartMessage.class) - .listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class); + .listener(IMultipartMessageListener.NoSubtypesListener.class).handles(IMultipartMessage.class) + .listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testStandardMessageListener(){ - ListenerFactory listeners = listeners( - StandardMessageListener.DefaultListener.class, - StandardMessageListener.DisabledListener.class, - StandardMessageListener.NoSubtypesListener.class); + @Test public void testStandardMessageListener() { + ListenerFactory listeners = listeners(StandardMessageListener.DefaultListener.class, StandardMessageListener.DisabledListener.class, + StandardMessageListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(StandardMessageListener.NoSubtypesListener.class).handles(StandardMessage.class) - .listener(StandardMessageListener.DefaultListener.class).handles(StandardMessage.class); + .listener(StandardMessageListener.NoSubtypesListener.class).handles(StandardMessage.class) + .listener(StandardMessageListener.DefaultListener.class).handles(StandardMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testICountableListener(){ - ListenerFactory listeners = listeners( - ICountableListener.DefaultListener.class, - ICountableListener.DisabledListener.class, - ICountableListener.NoSubtypesListener.class); + @Test public void testICountableListener() { + ListenerFactory listeners = listeners(ICountableListener.DefaultListener.class, ICountableListener.DisabledListener.class, + ICountableListener.NoSubtypesListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(ICountableListener.DefaultListener.class).handles(ICountable.class) - .listener(ICountableListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class); + .listener(ICountableListener.DefaultListener.class).handles(ICountable.class) + .listener(ICountableListener.DefaultListener.class) + .handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testMultipleMessageListeners(){ - ListenerFactory listeners = listeners( - ICountableListener.DefaultListener.class, - ICountableListener.DisabledListener.class, - IMultipartMessageListener.DefaultListener.class, - IMultipartMessageListener.DisabledListener.class, - MessagesListener.DefaultListener.class, - MessagesListener.DisabledListener.class); + @Test public void testMultipleMessageListeners() { + ListenerFactory listeners = listeners(ICountableListener.DefaultListener.class, ICountableListener.DisabledListener.class, + IMultipartMessageListener.DefaultListener.class, + IMultipartMessageListener.DisabledListener.class, MessagesListener.DefaultListener.class, + MessagesListener.DisabledListener.class); SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(ICountableListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class) - .listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class) - .listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class); + .listener(ICountableListener.DefaultListener.class) + .handles(MultipartMessage.class, IMultipartMessage.class, ICountable.class, StandardMessage.class) + .listener(IMultipartMessageListener.DefaultListener.class).handles(MultipartMessage.class, IMultipartMessage.class) + .listener(MessagesListener.DefaultListener.class).handles(MessageTypes.class); runTestWith(listeners, expectedSubscriptions); } - @Test - public void testOverloadedMessageHandlers(){ - ListenerFactory listeners = listeners( - Overloading.ListenerBase.class, - Overloading.ListenerSub.class); + @Test public void testOverloadedMessageHandlers() { + ListenerFactory listeners = listeners(Overloading.ListenerBase.class, Overloading.ListenerSub.class); SubscriptionManager subscriptionManager = new SubscriptionManager(1); ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1); - SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners) - .listener(Overloading.ListenerBase.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class) - .listener(Overloading.ListenerSub.class).handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class, Overloading.TestMessageB.class); + SubscriptionValidator expectedSubscriptions = new SubscriptionValidator(listeners).listener(Overloading.ListenerBase.class) + .handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class).listener(Overloading.ListenerSub.class) + .handles(Overloading.TestMessageA.class, Overloading.TestMessageA.class, Overloading.TestMessageB.class); runTestWith(listeners, expectedSubscriptions); } - private ListenerFactory listeners(Class ...listeners){ + private ListenerFactory listeners(Class... listeners) { ListenerFactory factory = new ListenerFactory(); - for (Class listener : listeners){ + for (Class listener : listeners) { factory.create(InstancesPerListener, listener); } return factory; } - private void runTestWith(final ListenerFactory listeners, final SubscriptionValidator validator){ + private void runTestWith(final ListenerFactory listeners, final SubscriptionValidator validator) { final SubscriptionManager subscriptionManager = new SubscriptionManager(1); ConcurrentExecutor.runConcurrent(TestUtil.subscriber(subscriptionManager, listeners), 1); diff --git a/src/test/java/dorkbox/util/messagebus/SynchronizedHandlerTest.java b/src/test/java/dorkbox/util/messagebus/SynchronizedHandlerTest.java index f960c84..8c29744 100644 --- a/src/test/java/dorkbox/util/messagebus/SynchronizedHandlerTest.java +++ b/src/test/java/dorkbox/util/messagebus/SynchronizedHandlerTest.java @@ -1,12 +1,11 @@ package dorkbox.util.messagebus; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Test; - import dorkbox.util.messagebus.annotations.Handler; import dorkbox.util.messagebus.annotations.Synchronized; import dorkbox.util.messagebus.common.MessageBusTest; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; /** * Todo: Add javadoc @@ -49,7 +48,7 @@ public class SynchronizedHandlerTest extends MessageBusTest { @Synchronized public void handleMessage(Object o){ counter.getAndIncrement(); -// System.err.println(counter.get()); +// System.err.println(counter.getSubscriptions()); } } } diff --git a/src/test/java/dorkbox/util/messagebus/common/SubscriptionValidator.java b/src/test/java/dorkbox/util/messagebus/common/SubscriptionValidator.java index ec81de3..16d2031 100644 --- a/src/test/java/dorkbox/util/messagebus/common/SubscriptionValidator.java +++ b/src/test/java/dorkbox/util/messagebus/common/SubscriptionValidator.java @@ -1,22 +1,15 @@ package dorkbox.util.messagebus.common; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import dorkbox.util.messagebus.SubscriptionManager; import dorkbox.util.messagebus.subscription.Subscription; +import dorkbox.util.messagebus.subscription.SubscriptionManager; + +import java.util.*; /** -* -* @author bennidi -* Date: 5/25/13 -*/ -public class SubscriptionValidator extends AssertSupport{ + * @author bennidi + * Date: 5/25/13 + */ +public class SubscriptionValidator extends AssertSupport { private List validations = new LinkedList(); @@ -27,11 +20,11 @@ public class SubscriptionValidator extends AssertSupport{ this.subscribedListener = subscribedListener; } - public Expectation listener(Class subscriber){ + public Expectation listener(Class subscriber) { return new Expectation(subscriber); } - private SubscriptionValidator expect(Class subscriber, Class messageType){ + private SubscriptionValidator expect(Class subscriber, Class messageType) { this.validations.add(new ValidationEntry(messageType, subscriber)); this.messageTypes.add(messageType); return this; @@ -39,8 +32,8 @@ public class SubscriptionValidator extends AssertSupport{ // match subscriptions with existing validation entries // for each tuple of subscriber and message type the specified number of listeners must exist - public void validate(SubscriptionManager manager){ - for (Class messageType : this.messageTypes){ + public void validate(SubscriptionManager manager) { + for (Class messageType : this.messageTypes) { Collection validationEntries = getEntries(messageType); // we split subs + superSubs into TWO calls. @@ -53,11 +46,11 @@ public class SubscriptionValidator extends AssertSupport{ assertEquals(validationEntries.size(), collection.size()); - for(ValidationEntry validationValidationEntry : validationEntries){ + for (ValidationEntry validationValidationEntry : validationEntries) { Subscription matchingSub = null; // one of the subscriptions must belong to the subscriber type - for(Subscription sub : collection){ - if(belongsTo(sub, validationValidationEntry.subscriber)){ + for (Subscription sub : collection) { + if (belongsTo(sub, validationValidationEntry.subscriber)) { matchingSub = sub; break; } @@ -92,11 +85,11 @@ public class SubscriptionValidator extends AssertSupport{ // } - private Collection getEntries(Class messageType){ + private Collection getEntries(Class messageType) { Collection matching = new LinkedList(); - for (ValidationEntry validationValidationEntry : this.validations){ + for (ValidationEntry validationValidationEntry : this.validations) { - if(validationValidationEntry.messageType.equals(messageType)) { + if (validationValidationEntry.messageType.equals(messageType)) { matching.add(validationValidationEntry); } } @@ -105,8 +98,7 @@ public class SubscriptionValidator extends AssertSupport{ - - public class Expectation{ + public class Expectation { private Class listener; @@ -114,14 +106,15 @@ public class SubscriptionValidator extends AssertSupport{ this.listener = listener; } - public SubscriptionValidator handles(Class ...messages){ - for(Class message : messages) { + public SubscriptionValidator handles(Class... messages) { + for (Class message : messages) { expect(this.listener, message); } return SubscriptionValidator.this; } } + private class ValidationEntry { diff --git a/src/test/java/dorkbox/util/messagebus/common/TestUtil.java b/src/test/java/dorkbox/util/messagebus/common/TestUtil.java index 44ba9d3..a32809f 100644 --- a/src/test/java/dorkbox/util/messagebus/common/TestUtil.java +++ b/src/test/java/dorkbox/util/messagebus/common/TestUtil.java @@ -1,11 +1,11 @@ package dorkbox.util.messagebus.common; -import java.util.Iterator; -import java.util.List; - import dorkbox.util.messagebus.MultiMBassador; import dorkbox.util.messagebus.PubSubSupport; -import dorkbox.util.messagebus.SubscriptionManager; +import dorkbox.util.messagebus.subscription.SubscriptionManager; + +import java.util.Iterator; +import java.util.List; /** * Todo: Add javadoc @@ -16,52 +16,48 @@ import dorkbox.util.messagebus.SubscriptionManager; public class TestUtil { - public static Runnable subscriber(final SubscriptionManager manager, final ListenerFactory listeners){ + public static Runnable subscriber(final SubscriptionManager manager, final ListenerFactory listeners) { final Iterator source = listeners.iterator(); return new Runnable() { - @Override - public void run() { + @Override public void run() { Object next; - while((next = source.next()) != null){ + while ((next = source.next()) != null) { manager.subscribe(next); } } }; } - public static Runnable unsubscriber(final SubscriptionManager manager, final ListenerFactory listeners){ + public static Runnable unsubscriber(final SubscriptionManager manager, final ListenerFactory listeners) { final Iterator source = listeners.iterator(); return new Runnable() { - @Override - public void run() { + @Override public void run() { Object next; - while((next = source.next()) != null){ + while ((next = source.next()) != null) { manager.unsubscribe(next); } } }; } - public static Runnable subscriber(final PubSubSupport bus, final ListenerFactory listeners){ + public static Runnable subscriber(final PubSubSupport bus, final ListenerFactory listeners) { final Iterator source = listeners.iterator(); return new Runnable() { - @Override - public void run() { + @Override public void run() { Object next; - while((next = source.next()) != null){ + while ((next = source.next()) != null) { bus.subscribe(next); } } }; } - public static Runnable unsubscriber(final PubSubSupport bus, final ListenerFactory listeners){ + public static Runnable unsubscriber(final PubSubSupport bus, final ListenerFactory listeners) { final Iterator source = listeners.iterator(); return new Runnable() { - @Override - public void run() { + @Override public void run() { Object next; - while((next = source.next()) != null){ + while ((next = source.next()) != null) { bus.unsubscribe(next); } } @@ -71,28 +67,25 @@ public class TestUtil { public static void setup(final PubSubSupport bus, final List listeners, int numberOfThreads) { Runnable[] setupUnits = new Runnable[numberOfThreads]; int partitionSize; - if(listeners.size() >= numberOfThreads){ - partitionSize = (int)Math.floor(listeners.size() / numberOfThreads); + if (listeners.size() >= numberOfThreads) { + partitionSize = (int) Math.floor(listeners.size() / numberOfThreads); } - else{ + else { partitionSize = 1; numberOfThreads = listeners.size(); } - for(int i = 0; i < numberOfThreads; i++){ + for (int i = 0; i < numberOfThreads; i++) { final int partitionStart = i * partitionSize; - final int partitionEnd = i+1 < numberOfThreads - ? partitionStart + partitionSize + 1 - : listeners.size(); + final int partitionEnd = i + 1 < numberOfThreads ? partitionStart + partitionSize + 1 : listeners.size(); setupUnits[i] = new Runnable() { private List listenerSubset = listeners.subList(partitionStart, partitionEnd); - @Override - public void run() { - for(Object listener : this.listenerSubset){ - bus.subscribe(listener); - } + @Override public void run() { + for (Object listener : this.listenerSubset) { + bus.subscribe(listener); + } } };