The consistently excellent Brian Goetz has written a new article in his Java Theory and Practice series entitled "Be a good (event) listener". Since listeners are an important part of the JMX API, here's how his advice applies there.

There are really two sets of recommendations, one for eventgenerators and one for event listeners.

In the JMX API, events are instances of Notification, event generators are instances of NotificationEmitter (or its ancestorNotificationBroadcaster), and event listeners are instances of NotificationListener.

The advice for event generators (NotificationEmitters) can be summarized as:

  • Be careful about concurrency between, on the one hand, sending an event to the list of listeners, and on the other hand, changing this list.
  • Ensure that if a listener throws aRuntimeException, that doesn't stop other listeners from getting the event.

Fortunately, the standard implementation class NotificationBroadcasterSupport already follows this advice. It uses a CopyOnWriteArrayList to manage the list of listeners. It catches a RuntimeException from any listener and does not let it affect later listeners or the sender of the event.

If you have an MBean that sends notifications, it is almost always best to use NotificationBroadcasterSupport. In the simplest case, you can simply extend this class. Since the Java language doesn't support multiple inheritance, sometimes you can't do this because you are already extending another class. In that case you can still use delegation, like this:

public class Something extends SomethingElse
        implements SomethingMBean, NotificationEmitter {

    private final NotificationBroadcasterSupport nbs =
        new NotificationBroadcasterSupport();
    private static final MBeanNotificationInfo[] notificationInfo = {...};

    public void addNotificationListener(NotificationListener nl,
                                        NotificationFilter nf,
                                        Object handback) {
        nbs.addNotificationListener(nl, nf, handback);
    }

    public MBeanNotificationInfo[] getNotificationInfo() {
        return notificationInfo.clone();
    }

    ...the two removeNotificationListener methods delegate like
       addNotificationListener...

    ...code that wants to send a notification does:
        Notification n = ...;
        nbs.sendNotification(n);
}

In Java SE 6 (Mustang),NotificationBroadcasterSupport will acquire a new constructor that allows you to specify theMBeanNotificationInfo[] array directly, so you can delegate the getNotificationInfo() method too.

Concerning event listeners, Brian Goetz has four pieces of advice:

  1. The listener shouldn't add itself from its own constructor.
  2. Ensure that you always remove listeners when they are no longer relevant.
  3. Listeners usually run in a separate thread so pay attention to thread safety.
  4. Listeners should not perform blocking operations.

I haven't noticed people doing the first thing in this list, but if you have, now you know you should stop.

The second is very good advice, and in particular you should pay attention to exceptions. You should be sure that everyaddNotificationListener is always matched by aremoveNotificationListener no matter what execution path the code follows.

Paying attention to thread safety is just as important for listeners in the JMX API as elsewhere.

Finally, listeners should not block. If a local listener blocks, meaning a listener that is in the same Java Virtual Machine as the MBean sending the notification, then that will usually cause the sending MBean to block too until the listener completes. If a remote listener blocks, it won't hold up the sending MBean, but it will prevent the delivery of any other notifications from the same connection until it finishes.

If you need to do a blocking operation, you won't want to use the invokeLater facility mentioned by Brian Goetz unless you are in a graphical application. The facilities from java.util.concurrent provide a good alternative. For example, you could change this listener...

class BlockingListener implements NotificationListener {
    public void handleNotification(Notification n, Object handback) {
        blockingOperation();
    }
}

...into this one...

class NonBlockingListener implements NotificationListener {
    private final Executor executor =
        Executors.newSingleThreadExecutor();

    public void handleNotification(Notification n, Object handback) {
        executor.execute(new Runnable() {
            public void run() {
                blockingOperation();
            }
        });
    }
}

This creates a private thread that will handle the call toblockingOperation(). If a second notification arrives while the thread is still doing theblockingOperation() of the first, then the secondblockingOperation() will be queued up and executed when the first has finished.

If you have many listeners where you need to do blocking operations like this, you might consider sharing anewSingleThreadExecutor between them, for example with a static field that they all access. You might also want to have a pool of threads rather than a single thread for the blocking operations; java.util.concurrent has everything you need for that.

Finally, if you have an MBean that absolutely must not block when it sends a notification, even in the face of a badly-behaved listener that does block, then Mustang provides another new constructor for NotificationBroadcasterSupportthat allows you to specify an Executor to be used for calling listeners. Again you can use the facilities ofjava.util.concurrent to control how this works. You might want to think about the worst case: a listener that never returns. You don't want this to always cause a new thread to be created for every notification, and you don't want it to always cause a new entry to be added to a queue of tasks for every notification either. So you're going to need to throw notifications away sometimes. You can achieve this with a ThreadPoolExecutor that has a bounded queue, a bounded number of threads, and a DiscardPolicy orDiscardOldestPolicy.