Programming Java threads in the
real world, Part
6 - JavaWorld -
March 1999
Tutorial Details:
Programming Java threads in the real world, Part 6
Programming Java threads in the real world, Part 6
By: By Allen Holub
The Observer pattern and mysteries of the AWTEventMulticaster
his month's column looks at the Observer pattern from a multithreaded perspective. Observer is one of the more useful design patterns if your goal is to loosen coupling relationships between classes. It's essential, really, for building truly reusable components. It turns out that, as usual, problems arise when threads are on the scene. For example, how do you handle simultaneous notifications on multiple threads? What about modifying the listener list from one thread while notifications are in progress on another? What happens when the notification is sent from one thread to an object that's being used by a second thread? You get the idea. This article will examine answers to these questions, and others.
Implementing Observer in a multithreaded world
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 .)
Observer-side problems: inner-class synchronization
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.
Listing 1 ( Status.java ): Listener-related race conditions
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import javax.swing.*;
import java.awt.event.*;
class Status extends JFrame
{
private String title = "Status: idle";
private String contents = "Nothing happening";
public Status()
{
JButton pop_it_up = new JButton( "Show status" );
pop_it_up.addActionListener
( new ActionListener()
{ public void actionPerformed( ActionEvent e )
{ JOptionPane.showMessageDialog( null,
contents, title, JOptionPane.INFORMATION_MESSAGE );
}
}
);
getContentPane().add( pop_it_up );
pack();
show();
}
public synchronized void change_status( String status, String explanation )
{ this.title = "Status: " + status;
this.contents = explanation;
}
public static void main( String[] args )
{
Status myself = new Status();
myself.change_status( "Busy", "I'm busy");
work();
myself.change_status( "Done", "I'm done");
work();
myself.change_status( "Busy", "I'm busy again");
work();
myself.change_status( "Done", "I'm done again");
work();
System.exit( 0 );
}
private static void work()
{ try{ Thread.currentThread().sleep(4000); }catch( Exception e ){}
}
}
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 message
myself.change_status( "Done", "I'm done doing something");
to the main-frame object. This method is synchronized, so it appears safe.
Half-way through the execution of 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.
To see what happens next, examine the event-handler setup code on line 13 . An anonymous inner-class object handles the button press, and it manufactures the message dialog using the title and contents fields. At this point, however, the title field has been modified, but the contents have not , so the displayed title doesn't jibe with the actual message.
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.
Notifier-side problems: notifications in a multithreaded world
Flipping the perspective over to that of the notifier, various thread-related problems emerge here, too:
Observers can be added and removed from the current list while notifications are in progress.
Events can occur so fast that several notifications go on at the same time, perhaps using different, but overlapping, observer lists.
Observers are notified even though they have been removed from the list of observers.
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 imp
Read
Tutorial at: Click here to view the tutorial
Rate Tutorial: Programming Java threads in the
real world, Part
6 - JavaWorld -
March 1999
View Tutorial: Programming Java threads in the
real world, Part
6 - JavaWorld -
March 1999
Related
Tutorials:
Programming Java threads in the
real world, Part
8
Programming Java threads in the
real world, Part
8 |
Object-oriented
language basics, Part
7
Object-oriented
language basics, Part
7 |
I want my AOP!, Part 1
I want my AOP!, Part 1 |
I want my AOP!, Part 3
I want my AOP!, Part 3 |
Achieve strong performance with threads,
Part 1
Achieve strong performance with threads,
Part 1 |
Achieve strong performance with threads,
Part 2
Achieve strong performance with threads,
Part 2 |
J2SE 1.4.1
boosts garbage
collection
J2SE 1.4.1
boosts garbage
collection |
Add concurrent processing with message-driven beans
Add concurrent processing with message-driven beans |
Create client-side user interfaces in HTML, Part
2
Create client-side user interfaces in HTML, Part
2 |
Impressive
!
Impressive
! |
Good, but
obsolete
Good, but
obsolete |
Real World HTML Parser
Real World HTML Parser
The two fundamental use-cases that are handled by the parser are extraction and transformation (the syntheses use-case, where HTML pages are created from scratch, is better handled by other tools closer to the source of data). Whil |
Asynchronous IO for Java
What is Asynchronous IO for Java?
Asynchronous IO for JavaTM (AIO4J) is a package that provides the capability to perform input and output (IO) on sockets and files asynchronously -- that is, where the Java application can request the operation but can |
Attribute-Oriented Programming with Java 1.5, Part 1
In this article, I will consider the case of a status-bar component embedded in a GUI application. I will explore a number of different ways to implement this status reporter, starting with the traditional hard-coded idiom. Along the way, I will introduce |
JDBC scripting, Part 2
JDBC scripting, Part 2
Programming and Java scripting in JudoScript
Summary
JudoScript is a rich functional scripting language, and an easy and powerful general programming and Java scripting language.
JudoScript's power comes from its synergy of |
Put JSF to work
Build a real-world Web application with JavaServer Faces, the Spring Framework, and Hibernate
Summary
Building a real-world Web application using JavaServer Faces is not a trivial task. This article shows you how to integrate JSF, the Spring Framewor |
The ABCs of Synchronization, Part 1
Threads may execute in a manner where their paths of execution are completely independent of each other. Neither thread depends upon the other for assistance. For example, one thread might execute a print job, while a second thread repaints a window. And |
Commons-Math: The Jakarta Mathematics Library
Commons-Math: The Jakarta Mathematics Library
The Java programming language and the math extensions in Commons Lang provide implementations for only the most basic mathematical algorithms. Routine development tasks such as computing basic statistics or s |
An Introduction to Java Object Persistence with EJB
The 'impedance mismatch' between relational databases' tabular orientation and object-oriented Java's hierarchical one is a perennial problem for which the Java world has several good solution offerings. This article, the first in a three-part series, wil |
Understanding MIDP System Threads
Describes the multi-threaded aspects of the J2ME application environment. Understanding the interactions between systems threads, user-interface and application threads will help in avoiding MIDlet deadlock. |
|
|
|