WIP - more even dist of performance (smaller up/down spikes). Using faster collections. SUB/UNSUB is write-locked
This commit is contained in:
parent
86d04c899c
commit
13cee46f6c
|
@ -1,6 +1,5 @@
|
|||
package net.engio.mbassy.multi;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -150,11 +149,9 @@ public class MultiMBassador implements IMessageBus {
|
|||
// this catches all exception types
|
||||
sub.publishToSubscription(this, deadMessage);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Collection<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass);
|
||||
// now get superClasses
|
||||
if (superSubscriptions != null) {
|
||||
|
@ -172,7 +169,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
|
||||
Class<?> messageClass1 = message1.getClass();
|
||||
Class<?> messageClass2 = message2.getClass();
|
||||
manager.readLock();
|
||||
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2);
|
||||
boolean validSubs = subscriptions != null && !subscriptions.isEmpty();
|
||||
|
||||
|
@ -183,8 +179,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
}
|
||||
|
||||
Collection<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2);
|
||||
Collection<Subscription> varArgs = manager.getVarArgs(messageClass1, messageClass2);
|
||||
manager.readUnLock();
|
||||
|
||||
|
||||
// Run subscriptions
|
||||
|
@ -212,27 +206,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
sub.publishToSubscription(this, message1, message2);
|
||||
}
|
||||
}
|
||||
|
||||
// now get varargs
|
||||
if (varArgs != null && !varArgs.isEmpty()) {
|
||||
// messy, but the ONLY way to do it.
|
||||
Object[] vararg = null;
|
||||
|
||||
for (Subscription sub : varArgs) {
|
||||
if (sub.isVarArg()) {
|
||||
if (vararg == null) {
|
||||
vararg = (Object[]) Array.newInstance(messageClass1, 2);
|
||||
vararg[0] = message1;
|
||||
vararg[1] = message2;
|
||||
|
||||
Object[] newInstance = new Object[1];
|
||||
newInstance[0] = vararg;
|
||||
vararg = newInstance;
|
||||
}
|
||||
sub.publishToSubscription(this, vararg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
|
@ -243,7 +216,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
Class<?> messageClass1 = message1.getClass();
|
||||
Class<?> messageClass2 = message2.getClass();
|
||||
Class<?> messageClass3 = message3.getClass();
|
||||
manager.readLock();
|
||||
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass1, messageClass2, messageClass3);
|
||||
boolean validSubs = subscriptions != null && !subscriptions.isEmpty();
|
||||
|
||||
|
@ -254,8 +226,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
}
|
||||
|
||||
Collection<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClass1, messageClass2, messageClass3);
|
||||
Collection<Subscription> varArgs = manager.getVarArgs(messageClass1, messageClass2, messageClass3);
|
||||
manager.readUnLock();
|
||||
|
||||
|
||||
// Run subscriptions
|
||||
|
@ -284,27 +254,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
}
|
||||
}
|
||||
|
||||
// now get varargs
|
||||
if (varArgs != null && !varArgs.isEmpty()) {
|
||||
// messy, but the ONLY way to do it.
|
||||
Object[] vararg = null;
|
||||
|
||||
for (Subscription sub : varArgs) {
|
||||
if (sub.isVarArg()) {
|
||||
if (vararg == null) {
|
||||
vararg = (Object[]) Array.newInstance(messageClass1, 3);
|
||||
vararg[0] = message1;
|
||||
vararg[0] = message2;
|
||||
vararg[0] = message3;
|
||||
|
||||
Object[] newInstance = new Object[1];
|
||||
newInstance[0] = vararg;
|
||||
vararg = newInstance;
|
||||
}
|
||||
sub.publishToSubscription(this, vararg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
|
@ -328,7 +277,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
}
|
||||
}
|
||||
|
||||
manager.readLock();
|
||||
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClasses);
|
||||
boolean validSubs = subscriptions != null && !subscriptions.isEmpty();
|
||||
|
||||
|
@ -338,13 +286,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
deadSubscriptions = manager.getSubscriptionsByMessageType(DeadMessage.class);
|
||||
}
|
||||
|
||||
// we don't support super subscriptions for var-args
|
||||
// Collection<Subscription> superSubscriptions = manager.getSuperSubscriptions(messageClasses);
|
||||
Collection<Subscription> varArgs = null;
|
||||
if (allSameType) {
|
||||
varArgs = manager.getVarArgs(messageClasses);
|
||||
}
|
||||
manager.readUnLock();
|
||||
|
||||
// Run subscriptions
|
||||
if (validSubs) {
|
||||
|
@ -372,27 +313,6 @@ public class MultiMBassador implements IMessageBus {
|
|||
// }
|
||||
// }
|
||||
|
||||
// now get varargs
|
||||
if (varArgs != null && !varArgs.isEmpty()) {
|
||||
// messy, but the ONLY way to do it.
|
||||
Object[] vararg = null;
|
||||
|
||||
for (Subscription sub : varArgs) {
|
||||
if (sub.isVarArg()) {
|
||||
if (vararg == null) {
|
||||
vararg = (Object[]) Array.newInstance(first, size);
|
||||
for (int i=0;i<size;i++) {
|
||||
vararg[i] = messages[i];
|
||||
}
|
||||
|
||||
Object[] newInstance = new Object[1];
|
||||
newInstance[0] = vararg;
|
||||
vararg = newInstance;
|
||||
}
|
||||
sub.publishToSubscription(this, vararg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -405,6 +325,26 @@ public class MultiMBassador implements IMessageBus {
|
|||
}
|
||||
};
|
||||
|
||||
// try {
|
||||
// this.dispatchQueue.put(runnable);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// // log.error(e);
|
||||
//
|
||||
// handlePublicationError(new PublicationError()
|
||||
// .setMessage("Error while adding an asynchronous message")
|
||||
// .setCause(e)
|
||||
// .setPublishedObject(message));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// int counter = 200;
|
||||
// while (!this.dispatchQueue.offer(runnable)) {
|
||||
// if (counter > 0) {
|
||||
// --counter;
|
||||
// LockSupport.parkNanos(1L);
|
||||
// } else {
|
||||
try {
|
||||
this.dispatchQueue.transfer(runnable);
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -416,6 +356,9 @@ public class MultiMBassador implements IMessageBus {
|
|||
.setCause(e)
|
||||
.setPublishedObject(message));
|
||||
}
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.engio.mbassy.multi.common;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
||||
|
@ -17,12 +18,12 @@ import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
|
|||
* @author bennidi
|
||||
* Date: 2/12/12
|
||||
*/
|
||||
public abstract class AbstractConcurrentSet<T> implements Collection<T> {
|
||||
public abstract class AbstractConcurrentSet<T> implements Set<T> {
|
||||
|
||||
// Internal state
|
||||
protected final ReentrantReadWriteUpdateLock lock = new ReentrantReadWriteUpdateLock();
|
||||
private final Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
|
||||
protected Entry<T> head; // reference to the first element
|
||||
protected final transient ReentrantReadWriteUpdateLock lock = new ReentrantReadWriteUpdateLock();
|
||||
private final transient Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
|
||||
protected transient Entry<T> head; // reference to the first element
|
||||
|
||||
protected AbstractConcurrentSet(Map<T, ISetEntry<T>> entries) {
|
||||
this.entries = entries;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package net.engio.mbassy.multi.common;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -21,6 +22,12 @@ public class IdentityObjectTree<KEY, VALUE> {
|
|||
public IdentityObjectTree() {
|
||||
}
|
||||
|
||||
// can be overridded to provide a custom backing map
|
||||
protected Map<KEY, IdentityObjectTree<KEY, VALUE>> createChildren() {
|
||||
// return new ConcurrentHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, 0.75f, 1);
|
||||
return new Reference2ReferenceOpenHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, 0.75f);
|
||||
}
|
||||
|
||||
public VALUE getValue() {
|
||||
VALUE returnValue = this.value;
|
||||
return returnValue;
|
||||
|
@ -219,7 +226,6 @@ public class IdentityObjectTree<KEY, VALUE> {
|
|||
return leaf;
|
||||
}
|
||||
|
||||
|
||||
public final IdentityObjectTree<KEY, VALUE> createLeaf(KEY key, VALUE value, boolean setValue) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
|
@ -228,7 +234,7 @@ public class IdentityObjectTree<KEY, VALUE> {
|
|||
IdentityObjectTree<KEY, VALUE> objectTree;
|
||||
|
||||
if (this.children == null) {
|
||||
this.children = new ConcurrentHashMap<KEY, IdentityObjectTree<KEY, VALUE>>(2, .8f, 1);
|
||||
this.children = createChildren();
|
||||
|
||||
// might as well add too
|
||||
objectTree = new IdentityObjectTree<KEY, VALUE>();
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package net.engio.mbassy.multi.common;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import net.engio.mbassy.multi.annotations.Handler;
|
||||
|
@ -20,7 +21,10 @@ public class ReflectionUtils
|
|||
|
||||
// modified by dorkbox, llc 2015
|
||||
public static Collection<Method> getMethods(Class<?> target) {
|
||||
Collection<Method> methods = new ArrayDeque<Method>();
|
||||
return getMethods(target, new StrongConcurrentSet<Method>());
|
||||
}
|
||||
|
||||
public static Collection<Method> getMethods(Class<?> target, Collection<Method> methods) {
|
||||
try {
|
||||
for (Method method : target.getDeclaredMethods()) {
|
||||
if (getAnnotation(method, Handler.class) != null) {
|
||||
|
@ -31,7 +35,7 @@ public class ReflectionUtils
|
|||
}
|
||||
|
||||
if (!target.equals(Object.class)) {
|
||||
methods.addAll(getMethods(target.getSuperclass()));
|
||||
getMethods(target.getSuperclass(), methods);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
@ -66,7 +70,8 @@ public class ReflectionUtils
|
|||
* @return A set of classes, each representing a super type of the root class
|
||||
*/
|
||||
public static Set<Class<?>> getSuperTypes(Class<?> from) {
|
||||
Set<Class<?>> superclasses = new HashSet<Class<?>>();
|
||||
Set<Class<?>> superclasses = new StrongConcurrentSet<Class<?>>(8, .8f);
|
||||
|
||||
collectInterfaces( from, superclasses );
|
||||
|
||||
while ( !from.equals( Object.class ) && !from.isInterface() ) {
|
||||
|
@ -104,7 +109,7 @@ public class ReflectionUtils
|
|||
* @param <A> Class of annotation type
|
||||
* @return Annotation instance or null
|
||||
*/
|
||||
private static <A extends Annotation> A getAnnotation( AnnotatedElement from, Class<A> annotationType, Set<AnnotatedElement> visited) {
|
||||
private static <A extends Annotation> A getAnnotation( AnnotatedElement from, Class<A> annotationType, Collection<AnnotatedElement> visited) {
|
||||
if( visited.contains(from) ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -123,7 +128,7 @@ public class ReflectionUtils
|
|||
}
|
||||
|
||||
public static <A extends Annotation> A getAnnotation( AnnotatedElement from, Class<A> annotationType){
|
||||
return getAnnotation(from, annotationType, new HashSet<AnnotatedElement>());
|
||||
return getAnnotation(from, annotationType, Collections.newSetFromMap(new Object2BooleanOpenHashMap<AnnotatedElement>(8, .8f)));
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.engio.mbassy.multi.common;
|
||||
import java.util.IdentityHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
|
@ -13,11 +14,12 @@ public class StrongConcurrentSet<T> extends AbstractConcurrentSet<T> {
|
|||
|
||||
|
||||
public StrongConcurrentSet() {
|
||||
this(16, .75f);
|
||||
this(16, 0.75f);
|
||||
}
|
||||
|
||||
public StrongConcurrentSet(int size, float lOAD_FACTOR) {
|
||||
super(new IdentityHashMap<T, ISetEntry<T>>(size));
|
||||
public StrongConcurrentSet(int size, float loadFactor) {
|
||||
// super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, 1));
|
||||
super(new Reference2ReferenceOpenHashMap<T, ISetEntry<T>>(size, loadFactor));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,33 +1,34 @@
|
|||
package net.engio.mbassy.multi.listener;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
|
||||
import net.engio.mbassy.multi.common.StrongConcurrentSet;
|
||||
|
||||
/**
|
||||
* All instances of any class that defines at least one message handler (see @MessageHandler) are message listeners. Thus,
|
||||
* a message listener is any object capable of receiving messages by means of defined message handlers.
|
||||
* There are no restrictions about the number of allowed message handlers in a message listener.
|
||||
* All instances of any class that defines at least one message handler (see @MessageHandler) are message listeners. Thus, a message
|
||||
* listener is any object capable of receiving messages by means of defined message handlers. There are no restrictions about the number of
|
||||
* allowed message handlers in a message listener.
|
||||
*
|
||||
* A message listener can be configured using the @Listener annotation but is always implicitly configured by the handler
|
||||
* definition it contains.
|
||||
* A message listener can be configured using the @Listener annotation but is always implicitly configured by the handler definition it
|
||||
* contains.
|
||||
*
|
||||
* This class is an internal representation of a message listener used to encapsulate all relevant objects
|
||||
* and data about that message listener, especially all its handlers.
|
||||
* There will be only one instance of MessageListener per message listener class and message bus instance.
|
||||
* This class is an internal representation of a message listener used to encapsulate all relevant objects and data about that message
|
||||
* listener, especially all its handlers. There will be only one instance of MessageListener per message listener class and message bus
|
||||
* instance.
|
||||
*
|
||||
* @author bennidi
|
||||
* Date: 12/16/12
|
||||
* @author bennidi Date: 12/16/12
|
||||
*/
|
||||
public class MessageListener {
|
||||
|
||||
private Collection<MessageHandler> handlers = new ArrayDeque<MessageHandler>();
|
||||
private final Collection<MessageHandler> handlers;
|
||||
private Class<?> listenerDefinition;
|
||||
|
||||
public MessageListener(Class<?> listenerDefinition) {
|
||||
public MessageListener(Class<?> listenerDefinition, int size) {
|
||||
this.handlers = new StrongConcurrentSet<MessageHandler>(size, 0.8F);
|
||||
this.listenerDefinition = listenerDefinition;
|
||||
}
|
||||
|
||||
public boolean isFromListener(Class<?> listener){
|
||||
public boolean isFromListener(Class<?> listener) {
|
||||
return this.listenerDefinition.equals(listener);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package net.engio.mbassy.multi.listener;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
|
||||
import net.engio.mbassy.multi.annotations.Handler;
|
||||
import net.engio.mbassy.multi.common.ReflectionUtils;
|
||||
import net.engio.mbassy.multi.common.StrongConcurrentSet;
|
||||
|
||||
/**
|
||||
* The meta data reader is responsible for parsing and validating message handler configurations.
|
||||
|
@ -23,19 +23,19 @@ public class MetadataReader {
|
|||
Collection<Method> allHandlers = ReflectionUtils.getMethods(target);
|
||||
|
||||
// retain only those that are at the bottom of their respective class hierarchy (deepest overriding method)
|
||||
Collection<Method> bottomMostHandlers = new ArrayDeque<Method>();
|
||||
Collection<Method> bottomMostHandlers = new StrongConcurrentSet<Method>(allHandlers.size(), .8F);
|
||||
for (Method handler : allHandlers) {
|
||||
if (!ReflectionUtils.containsOverridingMethod(allHandlers, handler)) {
|
||||
bottomMostHandlers.add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
MessageListener listenerMetadata = new MessageListener(target);
|
||||
MessageListener listenerMetadata = new MessageListener(target, bottomMostHandlers.size());
|
||||
|
||||
// for each handler there will be no overriding method that specifies @Handler annotation
|
||||
// but an overriding method does inherit the listener configuration of the overwritten method
|
||||
for (Method handler : bottomMostHandlers) {
|
||||
Handler handlerConfig = ReflectionUtils.getAnnotation( handler, Handler.class);
|
||||
Handler handlerConfig = ReflectionUtils.getAnnotation(handler, Handler.class);
|
||||
if (handlerConfig == null || !handlerConfig.enabled()) {
|
||||
continue; // disabled or invalid listeners are ignored
|
||||
}
|
||||
|
|
|
@ -37,9 +37,8 @@ public class Subscription {
|
|||
private final Collection<Object> listeners;
|
||||
|
||||
Subscription(MessageHandler handler) {
|
||||
// this.listeners = new WeakConcurrentSet<Object>();
|
||||
this.listeners = new StrongConcurrentSet<Object>();
|
||||
this.handlerMetadata = handler;
|
||||
this.listeners = new StrongConcurrentSet<Object>();
|
||||
|
||||
IHandlerInvocation invocation = new ReflectiveHandlerInvocation();
|
||||
if (handler.isSynchronized()) {
|
||||
|
@ -92,16 +91,17 @@ public class Subscription {
|
|||
Method handler = this.handlerMetadata.getHandler();
|
||||
IHandlerInvocation invocation = this.invocation;
|
||||
|
||||
for (Object listener : listeners) {
|
||||
try {
|
||||
for (Object listener : listeners) {
|
||||
invocation.invoke(listener, handler, message);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
errorHandler.handlePublicationError(new PublicationError()
|
||||
.setMessage("Error during invocation of message handler. " +
|
||||
"The class or method is not accessible")
|
||||
.setCause(e)
|
||||
.setMethodName(handler.getName())
|
||||
.setListener(listener)
|
||||
// .setListener(listener)
|
||||
.setPublishedObject(message));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handlePublicationError(new PublicationError()
|
||||
|
@ -110,7 +110,7 @@ public class Subscription {
|
|||
+ "Expected: " + handler.getParameterTypes()[0])
|
||||
.setCause(e)
|
||||
.setMethodName(handler.getName())
|
||||
.setListener(listener)
|
||||
// .setListener(listener)
|
||||
.setPublishedObject(message));
|
||||
} catch (InvocationTargetException e) {
|
||||
errorHandler.handlePublicationError(new PublicationError()
|
||||
|
@ -118,7 +118,7 @@ public class Subscription {
|
|||
"Message handler threw exception")
|
||||
.setCause(e)
|
||||
.setMethodName(handler.getName())
|
||||
.setListener(listener)
|
||||
// .setListener(listener)
|
||||
.setPublishedObject(message));
|
||||
} catch (Throwable e) {
|
||||
errorHandler.handlePublicationError(new PublicationError()
|
||||
|
@ -126,12 +126,11 @@ public class Subscription {
|
|||
"The handler code threw an exception")
|
||||
.setCause(e)
|
||||
.setMethodName(handler.getName())
|
||||
.setListener(listener)
|
||||
// .setListener(listener)
|
||||
.setPublishedObject(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void publishToSubscription(ErrorHandlingSupport errorHandler, Object message1, Object message2) {
|
||||
// Collection<Object> listeners = this.listeners.keySet();
|
||||
|
|
|
@ -40,7 +40,7 @@ public class SubscriptionManager {
|
|||
// all subscriptions per message type
|
||||
// this is the primary list for dispatching a specific message
|
||||
// write access is synchronized and happens only when a listener of a specific class is registered the first time
|
||||
private final Map<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle;
|
||||
private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle;
|
||||
private final IdentityObjectTree<Class<?>, Collection<Subscription>> subscriptionsPerMessageMulti;
|
||||
|
||||
// all subscriptions per messageHandler type
|
||||
|
@ -67,18 +67,10 @@ public class SubscriptionManager {
|
|||
private final ReentrantReadWriteUpdateLock LOCK = new ReentrantReadWriteUpdateLock();
|
||||
|
||||
public SubscriptionManager(int numberOfThreads) {
|
||||
this.MAP_STRIPING = 1;
|
||||
this.MAP_STRIPING = numberOfThreads;
|
||||
this.LOAD_FACTOR = 0.8f;
|
||||
|
||||
// this.subscriptionsPerMessageSingle = new IdentityHashMap<Class<?>, Collection<Subscription>>(4);
|
||||
// this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
||||
//
|
||||
// // only used during SUB/UNSUB
|
||||
// this.subscriptionsPerListener = new IdentityHashMap<Class<?>, Collection<Subscription>>(4);
|
||||
//
|
||||
// this.superClassesCache = new IdentityHashMap<Class<?>, Collection<Class<?>>>(8);
|
||||
|
||||
this.subscriptionsPerMessageSingle = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, this.MAP_STRIPING);
|
||||
this.subscriptionsPerMessageSingle = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, 1);
|
||||
this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
|
||||
|
||||
// only used during SUB/UNSUB
|
||||
|
@ -103,7 +95,7 @@ public class SubscriptionManager {
|
|||
Class<?> listenerClass = listener.getClass();
|
||||
Collection<Subscription> subscriptions;
|
||||
boolean nothingLeft = true;
|
||||
Lock UPDATE = this.LOCK.updateLock();
|
||||
Lock UPDATE = this.LOCK.writeLock();
|
||||
try {
|
||||
UPDATE.lock();
|
||||
|
||||
|
@ -168,10 +160,7 @@ public class SubscriptionManager {
|
|||
}
|
||||
|
||||
if (nothingLeft) {
|
||||
Lock WRITE = this.LOCK.writeLock();
|
||||
WRITE.lock();
|
||||
this.subscriptionsPerListener.remove(listenerClass);
|
||||
WRITE.unlock();
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
@ -181,6 +170,15 @@ public class SubscriptionManager {
|
|||
return;
|
||||
}
|
||||
|
||||
private final ThreadLocal<Collection<Subscription>> subInitialValue = new ThreadLocal<Collection<Subscription>>() {
|
||||
@Override
|
||||
protected java.util.Collection<Subscription> initialValue() {
|
||||
// return new ArrayDeque<Subscription>(8);
|
||||
// return Collections.newSetFromMap(new Reference2BooleanOpenHashMap<Subscription>(8, SubscriptionManager.this.LOAD_FACTOR));
|
||||
// return Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, SubscriptionManager.this.LOAD_FACTOR, SubscriptionManager.this.MAP_STRIPING));
|
||||
return new StrongConcurrentSet<Subscription>(8, SubscriptionManager.this.LOAD_FACTOR);
|
||||
}
|
||||
};
|
||||
|
||||
// when a class is subscribed, the registrations for that class are permanent in the "subscriptionsPerListener"?
|
||||
public void subscribe(Object listener) {
|
||||
|
@ -192,9 +190,9 @@ public class SubscriptionManager {
|
|||
}
|
||||
|
||||
Collection<Subscription> subscriptions;
|
||||
Lock UPDATE = this.LOCK.updateLock();
|
||||
Lock WRITE = this.LOCK.writeLock();
|
||||
try {
|
||||
UPDATE.lock();
|
||||
WRITE.lock();
|
||||
subscriptions = this.subscriptionsPerListener.get(listenerClass);
|
||||
|
||||
if (subscriptions != null) {
|
||||
|
@ -203,19 +201,19 @@ public class SubscriptionManager {
|
|||
subscription.subscribe(listener);
|
||||
}
|
||||
} else {
|
||||
Lock WRITE = this.LOCK.writeLock();
|
||||
try {
|
||||
WRITE.lock(); // upgrade updatelock to write lock, Avoid DCL
|
||||
|
||||
// a listener is subscribed for the first time
|
||||
Collection<MessageHandler> messageHandlers = this.metadataReader.getMessageListener(listenerClass).getHandlers();
|
||||
if (messageHandlers.isEmpty()) {
|
||||
int handlersSize = messageHandlers.size();
|
||||
|
||||
if (handlersSize == 0) {
|
||||
// remember the class as non listening class if no handlers are found
|
||||
this.nonListeners.put(listenerClass, this.holder);
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptions = new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR);
|
||||
} else {
|
||||
subscriptions = new StrongConcurrentSet<Subscription>(handlersSize, this.LOAD_FACTOR);
|
||||
// subscriptions = Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, this.LOAD_FACTOR, this.MAP_STRIPING));
|
||||
// subscriptions = Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, this.LOAD_FACTOR, 1));
|
||||
// subscriptions = Collections.newSetFromMap(new Reference2BooleanOpenHashMap<Subscription>(8, this.LOAD_FACTOR));
|
||||
this.subscriptionsPerListener.put(listenerClass, subscriptions);
|
||||
|
||||
resetSuperClassSubs();
|
||||
|
||||
|
@ -225,6 +223,11 @@ public class SubscriptionManager {
|
|||
Subscription subscription = new Subscription(messageHandler);
|
||||
subscription.subscribe(listener);
|
||||
|
||||
subscriptions.add(subscription);
|
||||
|
||||
//
|
||||
// save the subscription per message type
|
||||
//
|
||||
// single or multi?
|
||||
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
|
||||
int size = handledMessageTypes.length;
|
||||
|
@ -236,72 +239,72 @@ public class SubscriptionManager {
|
|||
|
||||
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
|
||||
if (subs == null) {
|
||||
// NOTE: Order is important for safe publication
|
||||
subs = new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR);
|
||||
subs.add(subscription);
|
||||
this.subscriptionsPerMessageSingle.put(clazz, subs);
|
||||
|
||||
Collection<Subscription> putIfAbsent = this.subscriptionsPerMessageSingle.putIfAbsent(clazz, this.subInitialValue.get());
|
||||
if (putIfAbsent != null) {
|
||||
subs = putIfAbsent;
|
||||
} else {
|
||||
subs.add(subscription);
|
||||
subs = this.subInitialValue.get();
|
||||
// this.subInitialValue.set(Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, this.LOAD_FACTOR, 1)));
|
||||
// this.subInitialValue.set(Collections.newSetFromMap(new Reference2BooleanOpenHashMap<Subscription>(8, this.LOAD_FACTOR)));
|
||||
this.subInitialValue.set(new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR));
|
||||
// this.subInitialValue.set(new ArrayDeque<Subscription>(8));
|
||||
}
|
||||
}
|
||||
|
||||
subs.add(subscription);
|
||||
|
||||
if (acceptsSubtypes) {
|
||||
// race conditions will result in duplicate answers, which we don't care about
|
||||
setupSuperClassCache(clazz);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// NOTE: Not thread-safe! must be synchronized in outer scope
|
||||
IdentityObjectTree<Class<?>, Collection<Subscription>> tree;
|
||||
|
||||
switch (size) {
|
||||
case 2: {
|
||||
tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes[0], handledMessageTypes[1]);
|
||||
if (acceptsSubtypes) {
|
||||
setupSuperClassCache(handledMessageTypes[0]);
|
||||
setupSuperClassCache(handledMessageTypes[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes[0], handledMessageTypes[1], handledMessageTypes[2]);
|
||||
if (acceptsSubtypes) {
|
||||
setupSuperClassCache(handledMessageTypes[0]);
|
||||
setupSuperClassCache(handledMessageTypes[1]);
|
||||
setupSuperClassCache(handledMessageTypes[2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes);
|
||||
if (acceptsSubtypes) {
|
||||
for (Class<?> c : handledMessageTypes) {
|
||||
setupSuperClassCache(c);
|
||||
// // NOTE: Not thread-safe! must be synchronized in outer scope
|
||||
// IdentityObjectTree<Class<?>, Collection<Subscription>> tree;
|
||||
//
|
||||
// switch (size) {
|
||||
// case 2: {
|
||||
// tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes[0], handledMessageTypes[1]);
|
||||
// if (acceptsSubtypes) {
|
||||
// setupSuperClassCache(handledMessageTypes[0]);
|
||||
// setupSuperClassCache(handledMessageTypes[1]);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// case 3: {
|
||||
// tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes[0], handledMessageTypes[1], handledMessageTypes[2]);
|
||||
// if (acceptsSubtypes) {
|
||||
// setupSuperClassCache(handledMessageTypes[0]);
|
||||
// setupSuperClassCache(handledMessageTypes[1]);
|
||||
// setupSuperClassCache(handledMessageTypes[2]);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// default: {
|
||||
// tree = this.subscriptionsPerMessageMulti.createLeaf(handledMessageTypes);
|
||||
// if (acceptsSubtypes) {
|
||||
// for (Class<?> c : handledMessageTypes) {
|
||||
// setupSuperClassCache(c);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Collection<Subscription> subs = tree.getValue();
|
||||
// if (subs == null) {
|
||||
// subs = new StrongConcurrentSet<Subscription>(16, this.LOAD_FACTOR);
|
||||
// tree.putValue(subs);
|
||||
// }
|
||||
// subs.add(subscription);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Subscription> subs = tree.getValue();
|
||||
if (subs == null) {
|
||||
subs = new StrongConcurrentSet<Subscription>(16, this.LOAD_FACTOR);
|
||||
tree.putValue(subs);
|
||||
}
|
||||
subs.add(subscription);
|
||||
}
|
||||
|
||||
subscriptions.add(subscription);
|
||||
}
|
||||
|
||||
this.subscriptionsPerListener.put(listenerClass, subscriptions);
|
||||
} finally {
|
||||
WRITE.unlock();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
UPDATE.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// must be protected by read lock
|
||||
// CAN RETURN NULL - not thread safe.
|
||||
|
@ -329,7 +332,6 @@ public class SubscriptionManager {
|
|||
}
|
||||
|
||||
|
||||
// must be protected by read lock
|
||||
// ALSO checks to see if the superClass accepts subtypes.
|
||||
public Collection<Subscription> getSuperSubscriptions(Class<?> superType) {
|
||||
Map<Class<?>, Collection<Subscription>> superClassSubs = this.superClassSubscriptions;
|
||||
|
@ -346,7 +348,8 @@ public class SubscriptionManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
subsPerType = new StrongConcurrentSet<Subscription>(16, this.LOAD_FACTOR);
|
||||
// subsPerType = new StrongConcurrentSet<Subscription>(types.size(), this.LOAD_FACTOR);
|
||||
subsPerType = new ArrayDeque<Subscription>(types.size() + 1);
|
||||
|
||||
for (Class<?> superClass : types) {
|
||||
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(superClass);
|
||||
|
@ -485,13 +488,19 @@ public class SubscriptionManager {
|
|||
return subsPerType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* race conditions will result in duplicate answers, which we don't care if happens
|
||||
*/
|
||||
private Collection<Class<?>> setupSuperClassCache(Class<?> clazz) {
|
||||
Collection<Class<?>> types = this.superClassesCache.get(clazz);
|
||||
|
||||
if (types == null) {
|
||||
// it doesn't matter if concurrent access stomps on values, since they are always the same.
|
||||
Set<Class<?>> superTypes = ReflectionUtils.getSuperTypes(clazz);
|
||||
types = new ArrayDeque<Class<?>>(superTypes);
|
||||
// types = new ArrayDeque<Class<?>>(superTypes);
|
||||
types = new StrongConcurrentSet<Class<?>>(superTypes.size(), this.LOAD_FACTOR);
|
||||
types.addAll(superTypes);
|
||||
|
||||
// race conditions will result in duplicate answers, which we don't care about
|
||||
this.superClassesCache.put(clazz, types);
|
||||
|
|
Loading…
Reference in New Issue
Block a user