|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 6 of 6
|
Now let's address the problem of the amount of time required to do the notifications, and the fact that the thread that calls
either publish() or publish_blocking() is effectively blocked until all the subscriber's receive() methods have been called. (Don't confuse locking with blocking. A thread can be blocked by attempting to enter a synchronized method that's in use by another thread. Acquiring a lock doesn't
cause you to block unless there's contention. A thread is also effectively blocked -- in the sense that it can't do anything
else -- while it's executing a lengthy method, whether or not that method is locked.)
The time problem is easily solved by wrapping threads around the publication code in Listing 7. I've done this in Listing 8. Now, the publish() method (Listing 8, line 16) creates a new Thread that notifies the subscribers. The complementary publish_sequentially() method (Listing 8, line 32) keeps the list locked while notifications are in progress, so if N threads publish simultaneously, you'll effectively go through the list N times, one after the other, instead of having N notification threads running in parallel.
Other, more complex solutions are also possible. It really isn't necessary for publish_sequentially() to create multiple threads when it's called while notifications are in progress. The publish_sequentially() method could just ignore any requests that come in while notifications are in progress, for example, or it could remember
the fact that the request was made and run through the list a second time when it finished the first traversal.
|
Though the various blocking-publication functions I've just presented do solve the notification-after-removal problem, they
aren't a general solution to the notification problem since any threads that modify the subscriber list are effectively blocked
until no notifications are in progress (which could be a long time). The notify-from-the-copy strategy is perfectly viable
when this blocking is unacceptable. The only down side is the amount of time that it takes not only to lock the subscription_list but also to actually make the copy. Fortunately, Sun's John Rose and Amy Fowler came up with an elegant solution to the problem:
the AWTEventMulticaster. The AWTEventMulticaster class implements all of the AWT/Swing listener interfaces, so you can literally pass an object of this class any message that can be sent to any
listener.
Figure 1 shows how an AWTEventMulticaster is used to construct a list of listeners. A static factory_method (add()) is used to create individual multicaster instances -- you can't create them with new. As you can see in Figure 1, this factory method is passed the current head-of-list reference and the listener to add. If either of these is null, it just returns the other, effectively creating a one-element list. If neither argument is null, then an AWTEventMulticaster object, essentially a binary-tree node, is created and initialized so that one child points to the existing list and the
other points to the newly added listener. It continues in this way, building a binary tree whose interior nodes are all AWTEventMulticasters and whose leaf notes are standard AWT or Swing listener objects.

Figure 1. Building a subscription list with a multicaster
Notification is done with a simple, recursive tree-traversal. (Which can be a problem. You'll note in Figure 1 that the tree will most likely degrade into a linked list, and recursive traversal of a linked list can use up a lot of runtime
stack.) As I mentioned earlier, the AWTEventMulticaster implements all of the standard listener interfaces (actionPerformed(), mouseClicked(), and so on). The multicaster overrides do nothing but pass the message to their children, using instanceof in the case of the leaf nodes to ensure that the message can be received by the actual object.
Immutable objects and blank finals
The most important feature of the AWTEventMulticaster is not actually evident in the diagram. The multicaster is an immutable object: all of its fields are declared static final, but are initialized in the constructor rather than the declaration; like this:
Since everything's final, the object's state can't change once it's created. A Java String is immutable, for example. Immutable objects have a significant advantage over normal objects in multithreaded environments:
you never have to synchronize access to them, because they can't change state. Consequently, replacing the Collection in the earlier listings with a multicaster eliminates the synchronization overhead entirely.
A final field that is initialized in the constructor rather than the declaration is called a blank final. You should use them whenever you can, which is unfortunately not as often as you'd like. An oft-reported bug that's been
around since the early beta versions of the JDK 1.1 compiler is still there in the Java 2 compiler: The compiler sometimes
incorrectly spits out the hard-error "Blank final may not have been initialized," even when the field in question has been
initialized. This bug tends to manifest only when you use a lot of inner classes. Because I don't want to corrupt the global
or package-level name space with class names that the user could care less about, I usually prefer removing the final to moving the inner class out to the global level.
Using the multicaster
The multicaster exhibits a lot of very desirable behavior. For example, you can safely add listeners to the list while traversals
are in progress because the tree is constructed from the bottom up. The new listeners will not be notified by any of the in-progress
notification threads, but adding a listener will not damage the list either.
Listener removal elicits the following question: How do you remove nodes from a tree whose nodes can't be modified? The flip
answer is that you build another tree. Figure 2 shows the easy scenario. To effectively delete node C, you create a new root node and make its child pointers reference node
D and node C's right subtree. If you overwrite subscribers with new_list -- the normal case -- there will be no more references to the gray-colored nodes, and they will eventually be garbage collected.

Figure 2. Deleting a node from a multicaster: the easy case
Figure 3 shows the more difficult process of deleting a node that's further down in the tree (C again). Here, you have to build a
second tree that looks exactly like that part of the original tree that's above the deleted node. As before, the gray colored
nodes are subject to garbage collection once subscribers is overwritten (and any traversals positioned in that part of the tree complete).

Figure 3. Deleting a node from a multicaster: the hard case
The first time I looked at the AWTEventMulticaster source, I thought the code was weird and unnecessarily complex. On reflection, though, I realized that there's a lot of neat
stuff here. The more I looked at it, the more I liked it. Multiple threads can happily add and remove nodes without conflicting
either with each other or with threads that are traversing the tree, and multiple threads can traverse the tree simultaneously.
Moreover, absolutely no synchronization is required to do any of this. The data structure that's used is no larger than the
doubly linked list that I used earlier, and the overhead of copying part of the tree when you do deletions is a lot less than
the overhead of copying the entire subscriber list every time you publish an event.
So, not being one to look a gift algorithm in the mouth, I wrote up my own general-purpose version of the multicaster, based
on the AWTEventMulticaster. It's in Listing 9. It's remarkable how little code there is here. (Ahh, the miracle of recursion!) All the complexity, in fact, is in the removal.
(I'll leave it as an exercise to the reader to puzzle through how it works -- opacity is the down side of most recursive algorithms.)
This implementation is very general purpose in that it can publish any Object to any class that implements the generic Subscriber interface (from Listing 2).
|
The final task, is to modify our publisher to use the multicaster. I've done that in Listing 10 (for this month's final entry in the worlds-most-complicated-way-to-print-"hello world" contest). This version is built on the one-thread-per-publication model discussed earlier.
|
So that's it for this month. The Observer pattern is enormously useful when you need to write code that can be used in any
context. Since the publisher knows nothing about the subscribers (except that they implement the Subscriber interface), the publisher is truly reusable in the technical OO sense. AWT/Swing obviously leverages Observer in its event
model, but I use this design pattern heavily in non-UI situations as well.
Next month I'll present the last of the class-related solutions to multithreading problems: a reader-writer lock that lets you control access to a shared global resource. I'll also discuss Critical Sections and Singletons. Subsequent columns will look at a few architectural solutions to threading problems such as Synchronous Dispatchers and Active Objects.