Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
More action with Struts 2
In a recent review of Struts 2 in Action, JW Blogger Oleg Mikheev notes that Struts 2 is "just a collection of extensions built upon WebWork, which is ultimately
the right thing to learn before starting a Struts 2 project." While Struts 2 has some architectural flaws, Oleg calls WebWork
well-designed, well-tested, and reliable. What are your experiences using Struts 2 and WebWork?
Also see "Hello World the WebWork way," a JavaWorld excerpt from WebWork in Action, by Patrick Lightbody and Jason Carreira.
| Memory Analysis in Eclipse |
| Enterprise AJAX - Transcend the Hype |
The AWT/Swing listeners are an example of a general-purpose design pattern called Observer (in the "Gang of Four book"). This design pattern is often called publisher/subscriber because publishing is a useful metaphor for describing how it works. Objects interested in finding out about some event subscribe to a publication administered by a publisher. The publisher notifies the subscribers that an event occurs by publishing it. To complete the metaphor, the event itself -- passed as an argument in the notification message -- is the publication. If you're familiar with the Observer pattern, you might want to skip to the next section.
The main intent of Observer is to decouple the generator of a message from the receiver of the message. For example, in the
following code, the Sender and Receiver classes are tightly coupled. You couldn't even compile Sender if Receiver didn't exist.
class Sender { Receiver listener; Sender( Receiver listener ){ this.listener = listener }; //... public void tickle(){ listener.hello(); } } class Receiver { public hello(){ System.out.println("Hello world"); } } //... Sender s = new Sender( new Receiver() ); s.tickle();
When the Sender is something as generic as a button, this tight coupling presents a problem. Were a button class actually to use this simple-notification
strategy, it could only notify one other class that it had been pressed -- not a good strategy for reuse. You can decouple
the button from the object to which it's talking by designing the button to talk to objects of an unknown (at least to the
button) class through a public interface. The sending class is then coupled to the interface, but not to the classes that implement that interface:
interface Observer { public void hello(); } class Sender2 { Observer listener; Sender2( Observer listener ){ this.listener = listener }; //... public void tickle(){ listener.hello() }; } class Receiver implements Observer { public hello(){ System.out.println("Hello world"); } } //... Sender2 s = new Sender2( new Receiver() ); s.tickle();
The main thing to notice is how similar the modified code is to the original code -- the design pattern imposes almost no
inconvenience with respect to coding. Writing in terms of an interface makes Sender2 much more flexible than Sender, since it can now talk to any class that implements the Observer interface.
A more-complicated implementation of the Observer pattern would use a multicast model where several observers would be notified when some event occurs rather than just one. Similarly, some mechanism could be added to add and remove observers after the object was created. AWT/Swing's delegation event model, of course, uses the Observer pattern to notify what Sun calls listeners (which are just observers) about various UI-related events. (The remainder of this article assumes some familiarity with the delegation event model.)
The first thread-related problem with Observer shows up when you implement a listener with an inner class. This example really drives home the fact that race conditions can appear even in situations where you have written no explicit multithreaded code at all because several of the Java packages (most notably AWT/Swing) create threads of their own.
I'll demonstrate. The Status class in Listing 1 does nothing but monitor a status that's modified by the (synchronized) change_status() (Listing 1, line 26). The Status object's UI is a single button, which, when pressed, pops up a message box that reports the current status. The main() method creates the Status object, then changes the status a few times, waiting for a few seconds between each change.
|
So, what's the problem? Everything's synchronized, isn't it? Well, not really. The problem is that, even though the word Thread appears nowhere in Listing 1, this is nonetheless a multithreaded app: there's the main thread (on which main() executes) and there's the AWT/Swing thread that handles GUI events like button presses (as discussed last month). Both threads are running in parallel and are accessing the same Status object. Now imagine the following sequence of events:
main() executes, popping up the main frame and setting the status message.main() finishes the first piece of work, and sends the messagemyself.change_status( "Done", "I'm done doing something");
to the main-frame object. This method is synchronized, so it appears safe.
change_status() (after the title is changed, but before the contents are changed) the user hits the "Show status" button. The main thread is preempted, and the AWT thread wakes up to process
the button press.A first (incorrect) attempt to solve the problem might be to synchronize the actionPerformed() method:
pop_it_up.addActionListener
( new ActionListener()
{ public synchronized void actionPerformed( ActionEvent e )
{ JOptionPane.showMessageDialog( null,
contents, title, JOptionPane.INFORMATION_MESSAGE );
}
}
);
This doesn't work, though. Remember, we have two objects and two monitors. Locking the inner-class object does not affect access to the outer-class object, which contains the two fields that are giving us grief. The only solution is to synchronize on the object that actually contains the fields that the two threads are accessing -- the outer-class object:
pop_it_up.addActionListener
( new ActionListener()
{ public void actionPerformed( ActionEvent e )
{ synchronized( Status.this )
{
JOptionPane.showMessageDialog( null,
contents, title, JOptionPane.INFORMATION_MESSAGE ); }
}
}
);
To be safe, all inner-class listeners that access outer-class fields should synchronize on the outer class object in this way.
Flipping the perspective over to that of the notifier, various thread-related problems emerge here, too:
Let's start by analyzing the modification-while-notifications-are-in-progress problem, which in some ways is the hardest to solve. AWT listeners can be added or removed at any time, even when notifications are in progress. In fact, a listener can even remove itself from a list of listeners as it services the notification message passed to it. The following code is perfectly legal:
Button some_button;
//...
some_button.addActionListener
( new ActionListener()
{ public void actionPerformed( ActionEvent e )
{ some_button.removeActionListener( this );
//...
}
}
);
I'll describe how to get control over this potentially chaotic situation by looking at various examples (which comprise this
month's entries in the world's-most-complicated-way-to-print-"hello world" contest). Listing 2 shows an implementation of the world's simplest observer/subscriber. The publication can be any arbitrary Object. (I don't like having to cast it all the time, but it's the price you pay for flexibility.) If you're interested in receiving
notices about something, you implement this interface, providing your own version of receive(), which is called when the event is fired off. In a more complex example, you might extend Subscriber with another interface that adds event-specific methods.
|
Listing 3 shows a publisher implementation that corresponds to the subscriber in Listing 2. It provides methods that let you add a new subscriber, remove a subscriber, and publish news of an event to the subscribers.
The publish() method (Listing 3, line 16) just publishes the string ("Hello world") to the subscribers.
Note that the list is copied by converting it to an array on line 20 of Listing 2. Unfortunately, you can't use clone() to copy a generic Collection. Of the two things you can do (make an empty list and explicitly copy the Collection into it or convert the Collection to an array), array conversion seems the most appropriate in the current code: an array is both smaller and easier to assemble
than another LinkedList.
I'll come back to this listing in a moment.
|
The Hello_world class in Listing 4 shows how the publisher and subscriber classes work together. main() manufactures a Publisher on line 5 and a Subscriber on line 6. The Subscriber implementation is an anonymous inner class whose receive() override prints the String that's passed in from the publisher as the publication argument ("Hello world"). In main(), the subscriber is forced to subscribe to the publication (this is not a democracy), and then the publisher is told to publish
the event.
|
Returning to Listing 3, you'll note that subscribe() and cancel_subscription() are synchronized, but publish() is not. Also note that publish() makes a copy of the subscription_list (on line 20) and notifies the subscribers by traversing the copy. This strategy is the one suggested in the JavaBeans specification for
handling notifications in a multithreaded environment, the point being that you don't want to lock up synchronized methods
of a publisher class while notifications are in progress. In any event, the new Java 2 Collection classes are (deliberately) not synchronized, so, subscribe() and cancel_subscription() must be synchronized in case one thread is trying to add a subscriber while another is removing one, or if two threads are adding
at the same time, and so on. (Yes, I know about the wrappers. I'll talk about them in a moment.)
Next, there is no way to tell how long it will take for a subscriber's receive() method to execute, since it's supplied by whoever implements the subscriber. Consequently, publish() executes for an indeterminate amount of time. If publish() were synchronized, then the entire object would be locked until the notifications completed. Any thread that attempted to
add or remove a subscriber, or call any other synchronized method for that matter, would block. So, publish() is not synchronized.
This lack of synchronization means that publish can't use the subscription_list directly, however. Otherwise another thread could come along and add and remove elements while notifications were in progress,
perhaps corrupting the Collection used for the subscription_list. The problem is solved simplistically by synchronizing long enough to make a clean copy, then working from the copy.