|
|
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 7 of 7
The third approach to making an object thread-safe is to embed that object in a thread-safe wrapper object. In this approach you leave the original class (which isn't thread-safe) unchanged and create a separate class that is thread-safe. Instances of the new class serve as thread-safe "front ends" to instances of the original class.
SafeRGBColor: A thread-safe wrapper
Here's an example of this approach applied to the very first version of RGBColor presented in this article.
// In file threads/ex1/SafeRGBColor.java
// Instances of this class are thread-safe
// wrappers of RGBColor objects, which are
// not thread-safe.
public class SafeRGBColor {
private RGBColor color;
public SafeRGBColor(int r, int g, int b) {
color = new RGBColor(r, g, b);
}
public synchronized void setColor(int r, int g, int b) {
color.setColor(r, g, b);
}
/**
* returns color in an array of three ints: R, G, and B
*/
public synchronized int[] getColor() {
return color.getColor();
}
public synchronized void invert() {
color.invert();
}
}
As mentioned earlier in this article, you don't want to make every class you design thread-safe -- only classes whose instances will be used concurrently by multiple threads. The reason you don't want to make every class thread-safe is that thread safety may involve a performance penalty. For example:
None of these performance setbacks are good excuses for neglecting to make classes that need to thread-safe so, but they do constitute good reasons not to make classes thread-safe unnecessarily.
Synchronizing critical sections
Marking your code's critical sections as synchronized is the "normal" approach to making classes synchronized. It is also
the only way to use wait() and notify() to get threads to cooperate towards achieving some common goal. So the guideline concerning Approach 1 is simply:
Unless special circumstances make it appropriate to use an immutable or wrapper object, use Approach 1 to make your class thread-safe: Make sure the appropriate instance variables are private and mark the critical sections as synchronized.
Using immutable objects
Achieving thread safety by making objects immutable (Approach 2) works well when objects are small and represent values of
a simple abstract data type. The Java API includes several examples of immutable objects, including String and the primitive type wrappers such as Integer, Long, Float, Boolean, Character, and so on.
It's worth noting that instances of the AWT's Color class are immutable. Likewise, the immutable approach may make sense for this article's RGBColor class, which is similar in functionality to the AWT's Color class, because RGBColor objects are small (they contain only 3 ints) and conceptually represent values of a simple abstract data type.
Another benefit of immutable objects is that you can pass references to them to methods without worrying that the method will
change the object's state. In addition, if the overhead of immutability (excessive creation of short-lived objects) may at
times be too inefficient, you can also define a mutable companion class that can be used when the immutable version isn't
appropriate. An example of this design approach in the Java API is the StringBuffer class, which serves as a mutable companion to the immutable String class. Note that the StringBuffer class is also thread-safe, but it uses the "normal" approach: its instance variables are private and its critical sections
are synchronized.
Using wrapper objects
The wrapper object approach to thread safety (Approach 3) makes the most sense when you want to give clients a choice between
a version of a class that is thread-safe and one that isn't. This approach also makes sense when you're a client of someone
else's class that isn't thread-safe, but you need to use the class in a multithreaded environment. Once you define your own
thread-safe wrapper for the class, you can safely use the class in a multithreaded environment by going through your wrapper.
A good example of this approach from the Java API comes from the 1.2 collections library. The 1.2 collections library defines
a hierarchy that includes classes that represent many kinds of collections -- none of which are thread-safe. But class Collection includes several class methods that will enclose a regular collection object in a thread-safe wrapper, so you can safely
use the object in a multithreaded context. This design gives users of the collections library a choice of using a collections
object that is thread-safe and one that isn't.
Note that a common attribute of wrapper classes like those you would use to add thread safety to the enclosed object is that the wrapper accepts the same messages as the enclosed object. In other words, often a wrapper class will descend from a common superclass or superinterface with the enclosed class. (For those of you familiar with the Design Patterns book by Gamma, et. al., this is the "decorator" pattern. See Resources for more information on this book.) This decorator design approach to wrappers, which is exhibited by the thread-safe wrappers of the 1.2 collections library, allows the thread safety to be dynamically added or removed from an object.
The advantage of the approach to wrapping taken by SafeRGBColor in this article is that thread safety is guaranteed when using a SafeRGBColor object, because the enclosed RGBColor object is created by SafeRGBColor's constructor and never returned by its own methods or passed to another object's methods. The decorator design approach,
because the enclosed object is instantiated by the client and passed to the constructor of the thread-safe wrapper, requires
that clients create the enclosed objects themselves first. Thus, to achieve thread safety, the decorator approach requires
that clients have the discipline not to use the enclosed object except through the thread-safe wrapper.
When you are designing the classes that compose a Java applet or application, your thread-safety decision should be based simply on whether or not each class will be exposed to potential write/write or read/write conflicts by your programs. To know whether or not such conflicts are possible, you just have to know how your program will work.
For example, I didn't choose thread safety for any of the classes that compose the two simulation applets included above in
this article, because they won't be exposed to multiple threads. Once the init() method of the applet has returned, the only thread that will be coursing through the veins of this code is the GUI event
handler thread -- and there is only one GUI event handler thread. As a result, even if a user frantically clicks the Reset
button as quickly as possible after the Step button, the code of my applet will handle the Step button press completely before
beginning to handle the Reset button press.
By contrast, I did make thread-safe certain classes that compose the JVM Simulation applets that are delivered on the CD-ROM of my JVM book (see Resources). These applets have Run and Stop buttons as well as Step and Reset buttons. When the user clicks Run, I fire off a thread that animates the applet, making the applet run as if the user were clicking Step about twice a second. When the user clicks Stop, the even handler thread comes in to stop the animation thread but mustn't be allowed to do so before the run thread completes its current step and puts the JVM simulator into a valid state.
If, instead of creating classes for an applet or application, you are creating classes for a library, either one that will be shared in-house or will serve as a product in its own right, you have a different problem. You may not know exactly how the classes will be used. In such cases, it may be a good idea to give clients a choice via the thread-safe wrapper approach.
The most important point to take away from this article is that when programming in Java, you should at least think about thread safety every time you design a class.
Here's a collection of the exception guidelines put forth by this article:
In next month's installment of Design Techniques, I'll continue the series of articles that focus on designing classes and objects. Next month's article, the seventh of this series, will discuss design guidelines that pertain to making an object observable.
I encourage your comments, criticisms, suggestions, flames -- all kinds of feedback -- about the material presented in this column. If you disagree with something, or have something to add, please let me know.
You can either participate in a discussion forum devoted to this material, enter a comment via the form at the bottom of the article, or e-mail me directly using the link provided in my bio below.
JVMSimulator and Method.java and search for sychronized. http://www.artima.com/insidejvm/applets/sourcecode.html