by Laurence Vanhelsuwรฉ

JavaBeans: properties, events, and thread safety

news
Sep 1, 199718 mins
ConcurrencyJava

Find out what measures you can take to ensure your beans survive in the real-life environment of a multithreaded application

Java is a dynamic language that includes easy-to-use multithreading language constructs and support classes. Many Java programs rely on multithreading to exploit internal application parallelism, improve networking performance, or speed up user feedback response. Most Java run-times use multithreading to implement Javaโ€™s garbage collection feature. Finally, the AWT also relies on separate threads for its functioning. In short, even the simplest of Java programs are born in an actively multithreading environment.

Java beans, therefore, are also deployed in such a dynamic, multithread environment, and herein lies the classic danger of encountering race conditions. Race conditions are timing-dependent program flow scenarios that can lead to state (program data) corruption. In the following section I will detail two such scenarios. Every Java bean needs to be designed with race conditions in mind so that a bean can withstand simultaneous use by several client threads.

Multithreading issues with simple properties

Bean implementations have to assume that multiple threads are accessing and/or modifying a single bean instance at the same time. As an example of an improperly implemented bean (as it relates to multithreading awareness), consider the following BrokenProperties bean and its associated MTProperties test program:

BrokenProperties.java

import java.awt.Point;

// Demo Bean that does not guard against multiple thread use.

public class BrokenProperties extends Point {

//------------------------------------------------------------------- // set()/get() for 'Spot' property //-------------------------------------------------------------------

public void setSpot(Point point) { // 'spot' setter this.x = point.x; this.y = point.y;

} public Point getSpot() { // 'spot' getter return this; } } // End of Bean/Class BrokenProperties

MTProperties.java

import java.awt.Point; import utilities.*; import utilities.beans.*;

public class MTProperties extends Thread {

protected BrokenProperties myBean; // the target bean to bash..

protected int myID; // each thread carries a bit of ID

//------------------------------------------------------------------- // main() entry point //------------------------------------------------------------------- public static void main (String[] args) {

BrokenProperties bean; Thread thread;

bean = (BrokenProperties) BeansKit.newBean("BrokenProperties");

for (int i=0; i < 20; i++) { // start 20 threads to bash bean thread = new MTProperties(bean, i); // threads get access to bean thread.start(); } } //------------------------------------------------------------------- // MTProperties Constructor //-------------------------------------------------------------------

public MTProperties(BrokenProperties bean, int id) { this.myBean = bean; // note the bean to address this.myID = id; // note who we are } //------------------------------------------------------------------- // the thread main loop: // do forever // create new random Point with x == y // tell bean to adopt Point as its new 'spot' property // ask bean what its 'spot' property is now set to // throw a wobbly if spot x does not equal spot y //------------------------------------------------------------------- public void run() { int someInt; Point point = new Point();

while ( true ) { someInt = (int) (Math.random()*100); point.x = someInt; point.y = someInt; myBean.setSpot( point );

point = myBean.getSpot(); if ( point.x != point.y ) { System.out.println("Bean corrupted ! x= " + point.x +", y= " + point.y); System.exit(10); } System.out.print( (char) ('A' + myID) ); System.out.flush(); } } } // End of Class MTProperties

Note: The utilities package imported by MTProperties contains reusable classes and static methods developed for the book by the author.

The two source code listings above define a bean called BrokenProperties and the class MTProperties, which is used to exercise the bean from within 20 running threads. Let us follow MTPropertiesโ€˜ main() entry point: First it instantiates a BrokenProperties bean, followed by the creation and starting of 20 threads. Class MTProperties extends java.lang.Thread, so all we need to do to turn class MTProperties into a thread is to override class Threadโ€˜s run() method. The constructor for our threads takes two arguments: the bean object the thread will communicate with and a unique identification, which allows the 20 threads to be easily differentiated at run-time.

The business end of this demo is our run() method in class MTProperties. Here we loop forever, creating random new (x,y) points, but with the following characteristic: their x coordinate always equals their y coordinate. These random points are passed to the beanโ€™s setSpot() setter method and then immediately read back using the getSpot() getter method. You would expect the read spot property to be identical to the random point created some milliseconds ago. Here is a sample output of the program when invoked at the command line:

ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHHHHHHHHHSSSSSSSSSSSSSS
IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFFFFFSSSSSSSSSSSSSSSSSS
FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMMMMMMMMMMMMMMMMMMMMMDD
JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTTTTTTTTTTTTTTTLPPPPPPP
PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSACCCCCCCCCCCCCCCCCCCKMD
QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIII
MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOOOQNNNNNNNNBTLPLRGFFFF
FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKKKKKKKKKKKCCCCCCCMMJAA 
AACBean corrupted ! x = 67, y = 13 
OOOOOOOOOOOOOOOOOOO

The output shows the 20 threads running in parallel (as far as a human observer goes); each thread uses the ID it received at construction time to print one of the letters A to T, the first 20 letters of the alphabet. As soon as any thread discovers that the read back spot property does not conform to the programmed characteristic of x = y, the thread prints a โ€œBean corruptedโ€ message and halts the experiment.

What you are seeing is the state-corrupting side effect of a race condition within the beanโ€™s setSpot() code. Here is that method again:

public void setSpot(Point point) {                 // 'spot' setter
    this.x = point.x;
    this.y = point.y;
}

What could ever go wrong in such a simple bit of code? Imagine thread A calling setSpot() with a point argument equal to (67,67). If we now slow down the clock of the Universe so that we can see the Java virtual machine (JVM) execute each Java statement, one at a time, we can imagine thread A executing the x coordinate copy statement (this.x = point.x;) and then, suddenly, thread A gets frozen by the operating system, and thread C is scheduled to run for a while. In its previous running state, thread C had just created its own new random point (13,13), called setSpot() itself, and then got frozen to make room for thread M, right after it had set the x coordinate to 13. So, the resumed thread C now carries on with its programmed logic: setting y to 13 and checking if the spot property is equal (13, 13), but finds that it has mysteriously changed to an illegal state of (67, 13); the x coordinate being half the state of what thread A was setting spot to, and the y coordinate being half the state of what thread C had set spot to. The end result is that the BrokenProperties bean ends up with an internally inconsistent state: a broken property.

Whenever a non-atomic data structure (that is, a structure consisting of more than one part) can be modified by more than one thread at a time, you need to protect the structure using a lock. In Java, this is done using the synchronized keyword.

Warning: Unlike all other Java types, note that Java does not guarantee that long and double are treated atomically! This is because long and double require 64 bits, which is twice the length of most modern CPU architecturesโ€™ word length (32 bits). Both loading and storing single machine words are intrinsically atomical operations, but moving 64-bit entities requires two such moves, and these are not protected by Java for the usual reason: performance. (Some CPUs allow the system bus to be locked to perform multiword transfers atomically, but this facility is not available on all CPUs and would, in any case, be incredibly expensive to apply to all long or double manipulations!) So, even when a property consists of just a single long or a single double, you should use full locking precautions to protect your longs or doubles from suddenly getting totally corrupted.

The synchronized keyword marks a block of code as being an atomic step. The code cannot be โ€œdivided,โ€ as when another thread interrupts the code to potentially reenter that block itself (hence the term reentrant code; all Java code should be reentrant). The solution for our BrokenProperties bean is trivial: just replace its setSpot() method with this:

public void setSpot(Point point) {               // 'spot' setter
    synchronized (this) {
           this.x = point.x;
           this.y = point.y;
    }
}

Or alternatively with this:

public synchronized void setSpot(Point point) {    // 'spot' setter
    this.x = point.x;
    this.y = point.y;
}

Both replacements are perfectly equivalent, although I prefer the first style because it shows more clearly what the exact function of the synchronized keyword is: a synchronized block is always linked to an object that gets locked. By locked I mean that the JVM first tries to obtain a lock (that is, exclusive access) on the object (that is, obtain exclusive access to it), or waits until the object becomes unlocked if it has been locked by another thread. The locking process guarantees that any object can only be locked (or owned) by one thread at a time.

So, the synchronized (this) syntax clearly echoes the internal mechanism: The argument inside the parentheses is the object to be locked (the current object) before the code block is entered. The alternative syntax, where the synchronized keyword is used as a modifier in a method signature, is simply a shorthand version of the former.

Warning: When static methods are marked synchronized, there is no this object to lock; only instance methods are associated with a current object. So, when class methods are synchronized, the java.lang.Class object corresponding to the methodโ€™s class is instead used to lock on. This approach has serious performance implications because a collection of class instances share a single associated Class object; whenever that Class object gets locked, all objects of that class (whether 3, 50, or 1000!) are barred from invoking the same static method. With this in mind, you should think twice before using synchronization with static methods.

In practice, always remember the explicit synchronized form because it allows you to โ€œatomizeโ€ the smallest possible block of code within a method. The shorthand form โ€œatomizesโ€ the entire method, which, for performance reasons, is often not what you want. Once a thread has entered an atomic block of code, no other thread that needs to execute any synchronized code on the same object can do so.

Tip: When a lock is obtained on an object, then all synchronized code for that objectโ€™s class will become atomic. Therefore, if your class contains more than one data structure that needs to be treated atomically, but those data structures are otherwise independent of each other, then another performance bottleneck can arise. Clients calling synchronized methods that manipulate one internal data structure will block all other clients that call the other methods that deal with any other atomic data structures of your class. Clearly, you should avoid such situations by splitting the class into smaller classes that handle only one data structure to be treated atomically at a time.

The JVM implements its synchronization feature by creating queues of threads waiting for an object to become unlocked. While this strategy is great when it comes to protecting the consistency of composite data structures, it can result in multithreaded traffic jams when a less-than-efficient section of code is marked as synchronized.

Therefore, always pay attention to just how much code you synchronize: it should be the absolute minimum necessary. For example, imagine our setSpot() method originally consisted of:

public void setSpot(Point point) {     // 'spot' setter
    log.println("setSpot() called on " + this.toString() );
    this.x = point.x;
    this.y = point.y;
}

Although the println statement might logically belong in the setSpot() method, it is not a part of the statement sequence that needs to be grouped into an atomic whole. Therefore, in this case, the right way to use the synchronized keyword would be as follows:

public void
setSpot(Point point) {     // 'spot' setter
    log.println("setSpot() called on " + this.toString() );
    synchronized (this) {
           this.x = point.x;
           this.y = point.y;
    }
}

The โ€œlazyโ€ way, and the approach you should avoid, looks like this:

public synchronized void setSpot(Point point) {    // 'spot' setter
    log.println("setSpot() called on " + this.toString() );
    this.x = point.x;
    this.y = point.y;
}

The first approach will lock out other threads for the absolute minimum amount of time, whereas the second approach will force other threads to also wait for something that is totally irrelevant to the locking operation: the println statement.

If, under the pressures of modern product schedules, you decided to use the lazy approach and made all your bean methods synchronized in toto, then all of your bean users would, in turn, end up with an equally lazy (read: low performance) bean.

Property listeners and multithreading

Multithreading introduces a whole spectrum of possible scenarios to complicate your life as a bean developer. Here is yet another scenario to consider: Race conditions (those again!) can result in some unexpected behavior stemming from incorrect

expectations

regarding the calling order

add&lt;ListenerType>

and

remove<ListenerType>

, and the actual timing of incoming events. Imagine a whole bunch of threads and a single bean with a bound property. If these threads constantly register, deregister, and modify that single beanโ€™s property (for reasons better known to themselves!), you can end up with a thread

legally

receiving a property change event while it has just recently deregistered itself (and therefore does not expect to receive any events). The following UnexpectedEvents bean and its MTListeners test program demonstrates this phenomenon:

UnexpectedEvents.java

import java.beans.*;

public class UnexpectedEvents {

protected PropertyChangeSupport butler; // butler does all the work

//------------------------------------------------------------------- // UnexpectedEvents Constructor //------------------------------------------------------------------- public UnexpectedEvents() { butler = new PropertyChangeSupport(this); } //------------------------------------------------------------------- // set()/get() for 'property' dummy property //------------------------------------------------------------------- public void setProperty( int value) { System.out.println("Bean changing property. Firing PC event.");

butler.firePropertyChange("property", new Integer(0), new Integer(1)); } public int getProperty() { return 1; } //------------------------------------------------------------------- // Listener registration methods //------------------------------------------------------------------- public void addPropertyChangeListener(PropertyChangeListener l) { butler.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { butler.removePropertyChangeListener(l); } } // End of Bean/Class UnexpectedEvents

MTListeners.java

import java.beans.*; import utilities.*; import utilities.beans.*;

public class MTListeners extends Thread implements PropertyChangeListener {

protected UnexpectedEvents myBean; // the target bean to bash..

protected int myID; // each thread carries a bit of ID

protected boolean readyForEvents = false; // have we registered yet?

//------------------------------------------------------------------- // main() entry point //------------------------------------------------------------------- public static void main (String[] args) { UnexpectedEvents bean; Thread thread;

bean = (UnexpectedEvents) BeansKit.newBean("UnexpectedEvents");

for (int i=0; i < 5; i++) { // start N threads to bash bean thread = new MTListeners(bean, i); // threads get access to bean thread.start(); } } //------------------------------------------------------------------- // MTListeners Constructor //------------------------------------------------------------------- public MTListeners(UnexpectedEvents bean, int id) { this.myBean = bean; // note the bean to address this.myID = id; // note who we are } //------------------------------------------------------------------- // the thread main loop: // do forever // register as listener, set flag to accept events // deregister as listener, set flag to refuse events // change bean property // // if we ever get an event while we shouldn't get one, say so and halt //------------------------------------------------------------------- public void run() {

while ( true ) { // register with bean, this means we might expect events from now // on. synchronized (this) { System.out.println(myID +" registering."); myBean.addPropertyChangeListener(this); readyForEvents = true; }

// let other threads run for a bit randomSleep();

// now deregister ourselves with bean, meaning we should not // receive any more events from bean from now on. synchronized (this) { System.out.println(myID +" un-registering."); myBean.removePropertyChangeListener(this); readyForEvents = false; } // let other threads run for a bit randomSleep();

// then modify the bean's property: System.out.println(myID +" about to change bean property.."); myBean.setProperty( (int) (Math.random()*50)); } } //------------------------------------------------------------------- // the implementation of the PropertyChangeListener interface //------------------------------------------------------------------- public void propertyChange(PropertyChangeEvent pcEvent) {

if ( readyForEvents == false ) { System.out.println("I ("+ myID +") got an event while I did not" + " expect to receive one!!"); System.exit(0); } System.out.println(myID +" heard of property change."); } //------------------------------------------------------------------- // utility function to put a thread to sleep for a random period of // less than 1 second. //------------------------------------------------------------------- protected void randomSleep() { MiscKit.delay( (int) (Math.random()*1000)); } } // End of Class MTListeners

As with our earlier MTProperties multithreading demonstration class, the heart of this demonstration is class MTListenersโ€˜ run() method where the threadโ€™s logic hides. What this method does is very banal: It keeps registering and deregistering its thread as a listener for the beanโ€™s property change event source, while also changing the beanโ€™s property and doing a lot of random sleeping. When you run MTListeners from the console, here is one possible output:

0 registering. 
2 registering. 
1 registering. 
3 registering. 
4 registering. 
4 un-registering. 
4 about to change bean property.. 
Bean changing property. Firing PC event. 
0 heard of property change. 
2 heard of property change. 1 heard of property change. 
3 heard of property change. 
1 un-registering. 
4 registering. 
0 un-registering. 
3 un-registering. 
2 un-registering. 
2 about to change bean property.. 
Bean changing property. Firing PC event. 
4 heard of property change. 
2 registering. 
1 about to change bean property.. 
Bean changing property. Firing PC event. 
4 heard of property change. 
2 heard of property change. 
1 registering. 
0 about to change bean property.. 
Bean changing property. Firing PC event. 
4 heard of property change. 
1 un-registering. 
2 heard of property change. 
I (1) got an event while I did not expect to receive one!! 

The program starts by spawning its thread clones, so the output starts with a sequence of threads telling us that they are registering as a listener with the bean. Note that even though the for loop spawns the threads in a strict 0-1-2-3โ€ฆ order, the output is already scrambled. This is due to the underlying operating systemโ€™s task or process scheduling decisions occurring at that particular time (when you run this program, the output will almost certainly be completely different), and is exactly the cause of the often unpredictable sequence of precise events in multithreaded systems.

Next in the output is thread 4, which is lucky enough to run long enough to register, deregister, and change the beanโ€™s property, all in the same time slice. Its changing of the beanโ€™s sole bound property triggers the firing of a PropertyChangeEvent to all currently registered listeners (threads 0 through 3). You can see this happening in the successive lines "x heard of property change."

As time goes on, the threads display their free-running character by running more and more interleaved (as seen in the output).

I now need you to pick up the thread again (pun intended) at the line reading "0 about to change bean property...โ€ At that point, threads 1, 2, and 4 are in a registered state, and shortly afterwards thread 4 already hears of the change, but thread 1 now unregisters itself before having heard of the change. Next, thread 2 receives the event and then Bang!โ€ฆthread 1, which is totally disinterested in any change events at this time because it deregistered itself, receives the property change event that thread 0 triggered all those steps ago.

Thread 1 was not registered as a listener for the event and still it received an event that it did not expect at the time. Now, if this were a bug in the PropertyChangeSupport.firePropertyChange() method, I would not go to such lengths pointing it out. The crux of the matter is that this is a legal side effect of the freedom of implementation given to implementers of event-firing methods. While iterating over the list of listeners, a firing method may or may not have previously frozen the list to protect it from changes while it is notifying the set of listeners. It is clear that in the case of PropertyChangeSupport.firePropertyChange() the method does freeze the list (by clone()ing the Vector containing all the listeners), which explains why thread 1 could remove itself as listener, and yet still receive an event moments later.

Warning: It is clear that multithreading can seriously stress the implementation of a bean. Therefore, it is of critical importance that you stress-test your beans to the maximum, using aggressive, far-fetched, and ruthless scenarios like the ones demonstrated here. Reality is usually even more creative when it comes to finding unlikely scenarios! If youโ€™ve attempted to reproduce the output of both test programs, youโ€™ve already seen how impossible this is, due to the multithreadingโ€™s unpredictable scheduling. The two test programs can run for minutes without showing race conditions. This reason alone should convince you that you should always test your beans in a fully multithreaded environment, not to mention allocate enough simulation time to uncover potential problems.

Conclusion

Java beans are not immune to the potentially corrupting effects of multithreading. Because your bean users will deploy your beans in multithreaded environments, meaning that there is a high probability that your bean will be addressed by multiple threads simultaneously, you have to protect your users from a brittle bean design that would break on such simultaneous access.

This article showed you two race condition scenarios, one leading to the corruption of a bean, the other leading to the corruption of a listener. These simulations clearly prove that if you, the bean developer, want your beans to stand any chance of surviving in the testing real-life environment of a multithreaded application, you need to take conscious measures to protect against the classic multithreading pitfall: race conditions.

Laurence Vanhelsuwรฉ is an independent software engineer. He is the typical self-taught wiz-kid who, after dropping out of higher education, started his professional career writing arcade games. Soon after, he worked on such diverse technologies as X.25 WAN routers, Virtual Reality flight simulation, Postscript and real-time digitized video-based traffic analysis. Laurence thinks Java is The Force that will be the Evil Empireโ€™s long-awaited crushing.