Thread safety simply means that the fields of an object or class always maintain a valid state, as observed by other objects and classes, even when used concurrently by multiple threads.There are two big reasons you need to think about thread safety when you design classes and objects in Java:
- Support for multiple threads is built into the Java language and API
- All threads inside a Java virtual machine (JVM) share the same heap and method area
Let see some example:
// Instances of this class are NOT 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);
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 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();
}
}
}
Because the three instance variables, ints r, g, and b, are private, the only way other classes and objects can access or influence the values of these variables is via RGBColor's constructor and methods. The design of the constructor and methods guarantees that:
- RGBColor's constructor will always give the variables proper initial values
- Methods setColor() and invert() will always perform valid state transformations on these variables
- Method getColor() will always return a valid view of these variables
Note that if bad data is passed to the constructor or the setColor() method, they will complete abruptly with an InvalidArgumentException. The checkRGBVals() method, which throws this exception, in effect defines what it means for an RGBColor object to be valid: the values of all three variables, r, g, and b, must be between 0 and 255, inclusive. In addition, in order to be valid, the color represented by these variables must be the most recent color either passed to the constructor or setColor() method, or produced by the invert() method.
If, in a single-threaded environment, you invoke setColor() and pass in blue, the RGBColor object will be blue when setColor() returns. If you then invoke getColor() on the same object, you'll get blue. In a single-threaded society, instances of this RGBColor class are well-behaved.
Throwing a concurrent wrench into the worksUnfortunately, this happy picture of a well-behaved RGBColor object can turn scary when other threads enter the picture. In a multithreaded environment, instances of the RGBColor class defined above are susceptible to two kinds of bad behavior: write/write conflicts and read/write conflicts.
Write/write conflictsImagine you have two threads, one thread named "red" and another named "blue." Both threads are trying to set the color of the same RGBColor object: The red thread is trying to set the color to red; the blue thread is trying to set the color to blue.
Both of these threads are trying to write to the same object's instance variables concurrently. If the thread scheduler interleaves these two threads in just the right way, the two threads will inadvertently interfere with each other, yielding a write/write conflict. In the process, the two threads will corrupt the object's state.
The Unsynchronized RGBColor appletThe following applet, named Unsynchronized RGBColor, demonstrates one sequence of events that could result in a corrupt RGBColor object. The red thread is innocently trying to set the color to red while the blue thread is innocently trying to set the color to blue. In the end, the RGBColor object represents neither red nor blue but the unsettling color, magenta.
Read/write conflictsAnother kind of misbehavior that may be exhibited in a multithreaded environment by instances of this RGBColor class is read/write conflicts. This kind of conflict arises when an object's state is read and used while in a temporarily invalid state due to the unfinished work of another thread.
There are basically three approaches you can take to make an object such as RGBThread thread-safe:- Synchronize critical sections
- Make it immutable
- Use a thread-safe wrapper
Approach 1: Synchronizing the critical sectionsThe most straightforward way to correct the unruly behavior exhibited by objects such as RGBColor when placed in a multi threaded context is to synchronize the object's critical sections. An object's critical sections are those methods or blocks of code within methods that must be executed by only one thread at a time. Put another way, a critical section is a method or block of code that must be executed atomically, as a single, indivisible operation. By using Java's synchronized keyword, you can guarantee that only one thread at a time will ever execute the object's critical sections.
To take this approach to making your object thread-safe, you must follow two steps: you must make all relevant fields private, and you must identify and synchronize all the critical sections.
// Thread safety through synchronization
// 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();
}
}
}
Approach 2: Immutable objectsAn 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.
// 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();
}
}
}
Approach 3: Thread-safe wrappersThe 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.
// Instances of this class are thread-safe wrappers of RGBColor objects, which are not thread-safe.
// 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?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
Summary:
- Given that thread safety can have a performance cost, don't make every class thread-safe -- only those classes that will actually be used concurrently by multiple threads.
- Don't avoid making classes thread-safe that need to be thread-safe out of fear of a performance impact.
- When making an object thread-safe via Approach 1, synchronize only the critical sections of the class.
- Use an immutable object especially if the object is small or represents a fundamental data type.
- If you can't change a non-thread-safe class, use a wrapper object.
- If you are creating a library of classes that will be used in both thread-safe and non-thread-safe requirements, consider making wrappers an option.