|
|
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
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: