Design for thread safety

Design tips on when and how to use synchronization, immutable objects, and thread-safe wrappers

1 2 3 Page 2
Page 2 of 3

The first rule to follow when making a class thread-safe through synchronizing its critical sections, therefore, is to make its fields private. Any field that you need to coordinate multithreaded access to must be private, otherwise it may be possible for other classes and objects to ignore your critical sections and access the fields directly.

Not every field must be private -- only those that will be involved in any temporarily invalid states created by the object's or class's critical sections. For example, constants (static final variables) can't be corrupted by multiple threads, so they needn't be private.

Step 2: Identify and synchronize critical sections

Once you've made the appropriate variables private, you need only mark the object's critical sections as synchronized. As mentioned above, a critical sectionis a bit of code that must be executed atomically, that is, as a single, indivisible operation. For example, the statements:

this.r = r;
this.g = g;
this.b = b;

must operate atomically for the setColor() method to behave as expected in a multithreaded environment. To ensure proper behavior, these three statements need to appear as if they were executed by a single, indivisible JVM instruction.

Note that reads and writes of primitive types and object references are atomic by definition, except for longs and doubles. This means that if you have an int, for example, that is independent of any other fields in an object, you needn't synchronize code that accesses that field. If two threads were to attempt to write two different values to the int concurrently, the resulting value would be one or the other. The int would never end up with a corrupted value made up of some bits written by one thread and other bits written by the other thread.

The same is not necessarily true, however, for longs and doubles. If two different threads were to attempt to write two different values to a long concurrently, you might just end up with a corrupted value consisting of some bits written by one thread and other bits written by the other thread. Multithreaded access to longs and doubles, therefore, should always be synchronized.

RGBColor # 2: Thread safety through synchronization

Here's a revised version of the RGBColor() class. This version, which has its critical sections marked as synchronized, is thread-safe:

// In file threads/ex2/RGBColor.java
// Instances of this class are thread-safe.
public class RGBColor {
    private int r;
    private int g;
    private int b;
    public RGBColor(int r, int g, int b) {
        checkRGBVals(r, g, b);
        this.r = r;
        this.g = g;
        this.b = b;
    }
    public void setColor(int r, int g, int b) {
        checkRGBVals(r, g, b);
        synchronized (this) {
            this.r = r;
            this.g = g;
            this.b = b;
        }
    }
    /**
    * returns color in an array of three ints: R, G, and B
    */
    public int[] getColor() {
        int[] retVal = new int[3];
        synchronized (this) {
            retVal[0] = r;
            retVal[1] = g;
            retVal[2] = b;
        }
        return retVal;
    }
    public synchronized void invert() {
        r = 255 - r;
        g = 255 - g;
        b = 255 - b;
    }
    private static void checkRGBVals(int r, int g, int b) {
        if (r < 0 || r > 255 || g < 0 || g > 255 ||
            b < 0 || b > 255) {
            throw new IllegalArgumentException();
        }
    }
}

The Synchronized RGBColor applet

The following applet, named Synchronized RGBColor, demonstrates a similar sequence of events to the one that led to a corrupt RGBColor object in the previous demonstration applet. This applet, however, shows how the thread-safe version of RGBColor is able to maintain a valid state, even when multiple threads are attempting to write to the object. As before, a red thread is trying to set the color to red while a blue thread is trying to set the color to blue. In the end, this RGBColor object represents not the invalid color magenta, but the valid -- and satisfying -- color red.

For some reason, your browser won't let you see this way-cool Java applet.

To step through the sequence of events that led to a corrupted RGBColor object, press the applet's Step button. Press Back to back up a step, and Reset to back up to the beginning. As you go, a line of text at the bottom of the applet will explain what's happening during each step.

For those of you who can't run the applet, here's a table that shows the sequence of events demonstrated by the applet:

ThreadStatementrgbColor
noneobject represents green02550 
blueblue thread invokes setColor(0, 0, 255)02550 
blueblue thread acquires lock02550 
bluecheckRGBVals(0, 0, 255);02550 
bluethis.r = 0;02550 
bluethis.g = 0;02550 
blueblue gets preempted000 
redred thread invokes setColor(255, 0, 0)000 
redred thread blocks because object locked000 
bluelater, blue thread continues000 
bluethis.b = 255000 
blueblue thread returns and releases lock00255 
redlater, red thread acquires lock and continues00255 
redcheckRGBVals(255, 0, 0);00255 
redthis.r = 255;00255 
redthis.g = 0;2550255 
redthis.b = 0;2550255 
redred thread returns and releases lock25500 
noneobject represents red25500 

Note that this version of RGBColor still has temporarily invalid states from time to time. To be specific, at times during the sequence shown above this object's state does represent the invalid states black and magenta. The trick to synchronization is that while an object is having one of those temporarily invalid moments, no other classes or objects are allowed to use or observe the state of the object via other threads.

Approach 2: Immutable objects

An alternative way to make an object thread-safe is to make the object immutable. An immutable object is one whose state can't be changed once the object is created.

Immutable objects are, by their very nature, thread-safe simply because threads have to be able to write to an object's instance variables to experience a read/write or write/write conflict. Because no methods (only the constructor) of an immutable object actually write to the object's instance variables, the object is by definition thread-safe.

In this approach to making an object thread-safe, you don't mark critical sections as synchronized. Instead, you separate out the critical sections that read instance variables from those that write to instance variables. The critical sections that read are left as-is. The critical sections that write must be changed so that, instead of altering the current object's instance variables, they create a new object that embodies the new state and returns a reference to that object.

RGBColor # 3: Thread safety through immutability

Here's an immutable version of RGBColor:

// In file threads/ex3/RGBColor.java
// Instances of this immutable class
// are thread-safe.
public class RGBColor {
    private final int r;
    private final int g;
    private final int b;
    public RGBColor(int r, int g, int b) {
        checkRGBVals(r, g, b);
        this.r = r;
        this.g = g;
        this.b = b;
    }
    /**
    * returns color in an array of three ints: R, G, and B
    */
    public int[] getColor() {
        int[] retVal = new int[3];
        retVal[0] = r;
        retVal[1] = g;
        retVal[2] = b;
        return retVal;
    }
    public RGBColor invert() {
        RGBColor retVal = new RGBColor(255 - r,
            255 - g, 255 - b);
        return retVal;
    }
    private static void checkRGBVals(int r, int g, int b) {
        if (r < 0 || r > 255 || g < 0 || g > 255 ||
            b < 0 || b > 255) {
            throw new IllegalArgumentException();
        }
    }
}

Note that the setColor() method is simply removed, as it doesn't make sense in an immutable RGBColor object. The getColor() method, which reads the instance variables, is identical to what it has been, except now it doesn't have to be synchronized. The invert() method, which writes to the instance variables, is changed. Instead of inverting the current object's color, this new invert() creates a new RGBColor object that represents the inverse of the object upon which invert() is invoked, and returns a reference to that object.

Approach 3: Thread-safe wrappers

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();
    }
}

Why not just synchronize everything?

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:

  • Synchronized method invocations generally are going to be slower than non-synchronized method invocations. In Sun's current JVM, for example, synchronized method invocations are 4 to 6 times slower than non-synchronized method invocations. In the future, the speed of synchronized method invocations should improve, but they will likely never achieve parity with non-synchronized method invocations.

  • Unnecessary synchronized method invocations (and synchronized blocks) can cause unnecessary blocking and unblocking of threads, which can hurt performance.

  • Immutable objects tend to be instantiated more often, leading to greater numbers of often short-lived objects that can increase the work of the garbage collector.

  • Synchronization gives rise to the possibility of deadlock, a severe performance problem in which your program appears to hang.

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.

Pros and cons of the three approaches to thread safety

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.

1 2 3 Page 2
Page 2 of 3