Cuncurrent sub/pub. 169ish ns/op VS 256 ns/op

This commit is contained in:
nathan 2015-02-20 19:54:53 +01:00
parent 2be96d7ff6
commit d0584d52b0
4 changed files with 255 additions and 226 deletions

View File

@ -44,7 +44,6 @@ public class MultiMBassador implements IMessageBus {
// this(2); // this(2);
} }
public MultiMBassador(int numberOfThreads) { public MultiMBassador(int numberOfThreads) {
if (numberOfThreads < 1) { if (numberOfThreads < 1) {
numberOfThreads = 1; // at LEAST 1 thread numberOfThreads = 1; // at LEAST 1 thread
@ -128,13 +127,11 @@ public class MultiMBassador implements IMessageBus {
} }
} }
@Override @Override
public void publish(Object message) { public void publish(Object message) {
SubscriptionManager manager = this.subscriptionManager; SubscriptionManager manager = this.subscriptionManager;
Class<?> messageClass = message.getClass(); Class<?> messageClass = message.getClass();
// manager.readLock();
Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass); Collection<Subscription> subscriptions = manager.getSubscriptionsByMessageType(messageClass);
// Run subscriptions // Run subscriptions
@ -144,9 +141,8 @@ public class MultiMBassador implements IMessageBus {
sub.publishToSubscription(this, message); sub.publishToSubscription(this, message);
} }
} else { } else {
// Dead Event must EXACTLY MATCH (no subclasses or varargs permitted) // Dead Event must EXACTLY MATCH (no subclasses)
Collection<Subscription> deadSubscriptions = manager.getSubscriptionsByMessageType(DeadMessage.class); Collection<Subscription> deadSubscriptions = manager.getSubscriptionsByMessageType(DeadMessage.class);
if (deadSubscriptions != null && !deadSubscriptions.isEmpty()) { if (deadSubscriptions != null && !deadSubscriptions.isEmpty()) {
DeadMessage deadMessage = new DeadMessage(message); DeadMessage deadMessage = new DeadMessage(message);
for (Subscription sub : deadSubscriptions) { for (Subscription sub : deadSubscriptions) {
@ -167,7 +163,6 @@ public class MultiMBassador implements IMessageBus {
sub.publishToSubscription(this, message); sub.publishToSubscription(this, message);
} }
} }
// manager.readUnLock();
} }
@SuppressWarnings("null") @SuppressWarnings("null")

View File

@ -18,7 +18,6 @@ public class StrongConcurrentSet<T> extends AbstractConcurrentSet<T> {
} }
public StrongConcurrentSet(int size, float loadFactor) { public StrongConcurrentSet(int size, float loadFactor) {
// super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, 1));
super(new Reference2ReferenceOpenHashMap<T, ISetEntry<T>>(size, loadFactor)); super(new Reference2ReferenceOpenHashMap<T, ISetEntry<T>>(size, loadFactor));
} }

View File

@ -91,47 +91,47 @@ public class Subscription {
int handleIndex = this.handlerMetadata.getMethodIndex(); int handleIndex = this.handlerMetadata.getMethodIndex();
IHandlerInvocation invocation = this.invocation; IHandlerInvocation invocation = this.invocation;
try { for (Object listener : listeners) {
for (Object listener : listeners) { try {
invocation.invoke(listener, handler, handleIndex, message); invocation.invoke(listener, handler, handleIndex, message);
} catch (IllegalAccessException e) {
e.printStackTrace();
// 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)
// .setPublishedObject(message));
} catch (IllegalArgumentException e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "Wrong arguments passed to method. Was: " + message.getClass()
// + "Expected: " + handler.getParameterTypes()[0])
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} catch (InvocationTargetException e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "Message handler threw exception")
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} catch (Throwable e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "The handler code threw an exception")
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} }
} catch (IllegalAccessException e) {
e.printStackTrace();
// 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)
// .setPublishedObject(message));
} catch (IllegalArgumentException e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "Wrong arguments passed to method. Was: " + message.getClass()
// + "Expected: " + handler.getParameterTypes()[0])
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} catch (InvocationTargetException e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "Message handler threw exception")
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} catch (Throwable e) {
e.printStackTrace();
// errorHandler.handlePublicationError(new PublicationError()
// .setMessage("Error during invocation of message handler. " +
// "The handler code threw an exception")
// .setCause(e)
// .setMethodName(handler.getName())
//// .setListener(listener)
// .setPublishedObject(message));
} }
} }
} }

View File

@ -10,7 +10,6 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.engio.mbassy.multi.common.IdentityObjectTree; import net.engio.mbassy.multi.common.IdentityObjectTree;
@ -40,7 +39,7 @@ public class SubscriptionManager {
// the metadata reader that is used to inspect objects passed to the subscribe method // the metadata reader that is used to inspect objects passed to the subscribe method
private final MetadataReader metadataReader = new MetadataReader(); private final MetadataReader metadataReader = new MetadataReader();
// all subscriptions per message type // all subscriptions per message type. We perpetually KEEP the types, as this lowers the amount of locking required
// this is the primary list for dispatching a specific message // 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 // write access is synchronized and happens only when a listener of a specific class is registered the first time
private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle; private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerMessageSingle;
@ -50,7 +49,7 @@ public class SubscriptionManager {
// this map provides fast access for subscribing and unsubscribing // this map provides fast access for subscribing and unsubscribing
// write access is synchronized and happens very infrequently // write access is synchronized and happens very infrequently
// once a collection of subscriptions is stored it does not change // once a collection of subscriptions is stored it does not change
private final Map<Class<?>, Collection<Subscription>> subscriptionsPerListener; private final ConcurrentHashMap<Class<?>, Collection<Subscription>> subscriptionsPerListener;
private final Object holder = new Object[0]; private final Object holder = new Object[0];
@ -78,7 +77,7 @@ public class SubscriptionManager {
this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>(); this.subscriptionsPerMessageMulti = new IdentityObjectTree<Class<?>, Collection<Subscription>>();
// only used during SUB/UNSUB // only used during SUB/UNSUB
this.subscriptionsPerListener = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, 1); this.subscriptionsPerListener = new ConcurrentHashMap<Class<?>, Collection<Subscription>>(4, this.LOAD_FACTOR, this.MAP_STRIPING);
this.superClassesCache = new ConcurrentHashMap<Class<?>, FastEntrySet<Class<?>>>(8, this.LOAD_FACTOR, this.MAP_STRIPING); this.superClassesCache = new ConcurrentHashMap<Class<?>, FastEntrySet<Class<?>>>(8, this.LOAD_FACTOR, this.MAP_STRIPING);
@ -91,92 +90,69 @@ public class SubscriptionManager {
this.superClassSubscriptions = new ConcurrentHashMap<Class<?>, FastEntrySet<Subscription>>(8, this.LOAD_FACTOR, this.MAP_STRIPING); this.superClassSubscriptions = new ConcurrentHashMap<Class<?>, FastEntrySet<Subscription>>(8, this.LOAD_FACTOR, this.MAP_STRIPING);
} }
public void readLock() {
this.LOCK.readLock().lock();
}
public void readUnLock() {
this.LOCK.readLock().unlock();
}
public void unsubscribe(Object listener) { public void unsubscribe(Object listener) {
if (listener == null) { if (listener == null) {
return; return;
} }
Class<?> listenerClass = listener.getClass(); Class<?> listenerClass = listener.getClass();
Collection<Subscription> subscriptions; // these are a concurrent collection
boolean nothingLeft = true; Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
Lock UPDATE = this.LOCK.writeLock();
try {
UPDATE.lock();
subscriptions = this.subscriptionsPerListener.get(listenerClass); if (subscriptions != null) {
for (Subscription subscription : subscriptions) {
subscription.unsubscribe(listener);
if (subscriptions != null) { // boolean isEmpty = subscription.isEmpty();
for (Subscription subscription : subscriptions) { //
subscription.unsubscribe(listener); // if (isEmpty) {
// // single or multi?
boolean isEmpty = subscription.isEmpty(); // Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
// int size = handledMessageTypes.length;
if (isEmpty) { // if (size == 1) {
// single or multi? // // single
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes(); // Class<?> clazz = handledMessageTypes[0];
int size = handledMessageTypes.length; //
if (size == 1) { // // NOTE: Order is important for safe publication
// single // Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
Class<?> clazz = handledMessageTypes[0]; // if (subs != null) {
// subs.remove(subscription);
// NOTE: Order is important for safe publication //
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz); // if (subs.isEmpty()) {
if (subs != null) { // // remove element
subs.remove(subscription); // this.subscriptionsPerMessageSingle.remove(clazz);
//
if (subs.isEmpty()) { // resetSuperClassSubs();
// remove element // }
this.subscriptionsPerMessageSingle.remove(clazz); // }
// } else {
resetSuperClassSubs(); // // NOTE: Not thread-safe! must be synchronized in outer scope
} // IdentityObjectTree<Class<?>, Collection<Subscription>> tree;
} //
} else { // switch (size) {
// NOTE: Not thread-safe! must be synchronized in outer scope // case 2: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[0], handledMessageTypes[1]); break;
IdentityObjectTree<Class<?>, Collection<Subscription>> tree; // case 3: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break;
// default: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes); break;
switch (size) { // }
case 2: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[0], handledMessageTypes[1]); break; //
case 3: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break; // if (tree != null) {
default: tree = this.subscriptionsPerMessageMulti.getLeaf(handledMessageTypes); break; // Collection<Subscription> subs = tree.getValue();
} // if (subs != null) {
// subs.remove(subscription);
if (tree != null) { //
Collection<Subscription> subs = tree.getValue(); // if (subs.isEmpty()) {
if (subs != null) { // // remove tree element
subs.remove(subscription); // switch (size) {
// case 2: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[0], handledMessageTypes[1]); break;
if (subs.isEmpty()) { // case 3: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break;
// remove tree element // default: this.subscriptionsPerMessageMulti.remove(handledMessageTypes); break;
switch (size) { // }
case 2: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[0], handledMessageTypes[1]); break; // }
case 3: this.subscriptionsPerMessageMulti.remove(handledMessageTypes[1], handledMessageTypes[1], handledMessageTypes[2]); break; // }
default: this.subscriptionsPerMessageMulti.remove(handledMessageTypes); break; // }
} // }
} // }
}
}
}
}
nothingLeft &= isEmpty;
}
} }
if (nothingLeft) {
this.subscriptionsPerListener.remove(listenerClass);
}
} finally {
UPDATE.unlock();
} }
return; return;
@ -201,121 +177,180 @@ public class SubscriptionManager {
return; return;
} }
Collection<Subscription> subscriptions; Collection<Subscription> subscriptions = this.subscriptionsPerListener.get(listenerClass);
Lock WRITE = this.LOCK.writeLock(); if (subscriptions == null) {
try { // a listener is subscribed for the first time
WRITE.lock(); Collection<MessageHandler> messageHandlers = this.metadataReader.getMessageListener(listenerClass).getHandlers();
subscriptions = this.subscriptionsPerListener.get(listenerClass); int handlersSize = messageHandlers.size();
if (subscriptions != null) { if (handlersSize == 0) {
// subscriptions already exist and must only be updated // remember the class as non listening class if no handlers are found
for (Subscription subscription : subscriptions) { this.nonListeners.put(listenerClass, this.holder);
subscription.subscribe(listener);
}
} else { } else {
// a listener is subscribed for the first time Collection<Subscription> putIfAbsent = this.subscriptionsPerListener.putIfAbsent(listenerClass, this.subInitialValue.get());
Collection<MessageHandler> messageHandlers = this.metadataReader.getMessageListener(listenerClass).getHandlers(); if (putIfAbsent != null) {
int handlersSize = messageHandlers.size(); subscriptions = putIfAbsent;
if (handlersSize == 0) {
// remember the class as non listening class if no handlers are found
this.nonListeners.put(listenerClass, this.holder);
} else { } else {
subscriptions = new StrongConcurrentSet<Subscription>(handlersSize, this.LOAD_FACTOR); subscriptions = this.subInitialValue.get();
// subscriptions = Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, this.LOAD_FACTOR, this.MAP_STRIPING)); this.subInitialValue.set(new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR));
// 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(); resetSuperClassSubs();
// create NEW subscriptions for all detected message handlers // create NEW subscriptions for all detected message handlers
for (MessageHandler messageHandler : messageHandlers) { for (MessageHandler messageHandler : messageHandlers) {
// create the subscription // create the subscription
Subscription subscription = new Subscription(messageHandler); Subscription subscription = new Subscription(messageHandler);
subscription.subscribe(listener); subscription.subscribe(listener);
subscriptions.add(subscription); subscriptions.add(subscription);
// //
// save the subscription per message type // save the subscription per message type
// //
// single or multi? Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes();
Class<?>[] handledMessageTypes = subscription.getHandledMessageTypes(); int size = handledMessageTypes.length;
int size = handledMessageTypes.length; boolean acceptsSubtypes = subscription.acceptsSubtypes();
boolean acceptsSubtypes = subscription.acceptsSubtypes();
if (size == 1) { if (size == 1) {
// single // single
Class<?> clazz = handledMessageTypes[0]; Class<?> clazz = handledMessageTypes[0];
Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz); Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
if (subs == null) { if (subs == null) {
Collection<Subscription> putIfAbsent = this.subscriptionsPerMessageSingle.putIfAbsent(clazz, this.subInitialValue.get()); putIfAbsent = this.subscriptionsPerMessageSingle.putIfAbsent(clazz, this.subInitialValue.get());
if (putIfAbsent != null) { if (putIfAbsent != null) {
subs = putIfAbsent; subs = putIfAbsent;
} else { } else {
subs = this.subInitialValue.get(); subs = this.subInitialValue.get();
// this.subInitialValue.set(Collections.newSetFromMap(new ConcurrentHashMap<Subscription, Boolean>(8, this.LOAD_FACTOR, 1))); this.subInitialValue.set(new StrongConcurrentSet<Subscription>(8, this.LOAD_FACTOR));
// 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 subs.add(subscription);
// IdentityObjectTree<Class<?>, Collection<Subscription>> tree;
// if (acceptsSubtypes) {
// switch (size) { // race conditions will result in duplicate answers, which we don't care about
// case 2: { setupSuperClassCache(clazz);
// 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);
} }
} }
} }
} }
} finally { } else {
WRITE.unlock(); // subscriptions already exist and must only be updated
for (Subscription subscription : subscriptions) {
subscription.subscribe(listener);
}
} }
//
// if (subscriptions != null) {
// // subscriptions already exist and must only be updated
// for (Subscription subscription : subscriptions) {
// subscription.subscribe(listener);
// }
// }
// else {
// // a listener is subscribed for the first time
// Collection<MessageHandler> messageHandlers = this.metadataReader.getMessageListener(listenerClass).getHandlers();
// 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);
// } 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();
//
// // create NEW subscriptions for all detected message handlers
// for (MessageHandler messageHandler : messageHandlers) {
// // create the subscription
// 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;
// boolean acceptsSubtypes = subscription.acceptsSubtypes();
//
// if (size == 1) {
// // single
// Class<?> clazz = handledMessageTypes[0];
//
// Collection<Subscription> subs = this.subscriptionsPerMessageSingle.get(clazz);
// if (subs == null) {
// Collection<Subscription> putIfAbsent = this.subscriptionsPerMessageSingle.putIfAbsent(clazz, this.subInitialValue.get());
// if (putIfAbsent != null) {
// subs = putIfAbsent;
// } else {
// 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);
//// }
//// }
//// break;
//// }
//// }
////
//// Collection<Subscription> subs = tree.getValue();
//// if (subs == null) {
//// subs = new StrongConcurrentSet<Subscription>(16, this.LOAD_FACTOR);
//// tree.putValue(subs);
//// }
//// subs.add(subscription);
// }
// }
// }
// }
} }
// must be protected by read lock // must be protected by read lock