Chapter 7. Concurrent Access to Objects and Variables

IN THIS CHAPTER

When multiple threads are interacting with an object, controls need to be in place to ensure that the threads don't adversely affect one another. This chapter deals with issues that can introduce subtle errors in your application. An application that fails to safely control concurrent access can work properly most of the time—maybe nearly all the time—but will occasionally produce erroneous results. This makes the understanding and disciplined use of the information in this chapter critical to writing truly thread-safe applications that work properly all the time.

In this chapter, I'll show you when and how to use the synchronized and volatile keywords to control concurrent access to objects and variables.

volatile Member Variable Modifier

The volatile keyword is used as a modifier on member variables to force individual threads to reread the variable's value from shared memory every time the variable is accessed. In addition, individual threads are forced to write changes back to shared memory as soon as they occur. This way, two different threads always see the same value for a member variable at any particular time. Chances are that most of you expected this behavior from the Java VM already. In fact, many experienced Java developers don't understand when the use of volatile is necessary.

The Java Language Specification indicates that for optimal speed, individual threads are permitted to keep a working copy of shared member variables and only reconcile them with the shared original occasionally. To be more accurate, the word "occasionally" in the last sentence should be replaced with "when a thread enters or leaves a synchronized block of code." I'll tell you more about synchronized blocks later in this chapter. When only one thread is interacting with the member variables of an object, this optimization works very well and can allow for faster execution. When two (or more) threads are simultaneously working with an object, care must be taken to ensure that changes made to a shared member variable by one thread are seen by the other.

The volatile keyword is used to tell the VM that it should not keep a private copy of a variable and should instead interact directly with the shared copy. The class Volatile, shown in Listing 7.1, shows the behavior when the volatile keyword is and is not used.

Example 7.1. Volatile.java—Demonstration Showing How volatile Makes a Difference

  1: public class Volatile extends Object implements Runnable {
  2:     // not marked as 'volatile', but it should be!
  3:     private int value; 
  4:
  5:     private volatile boolean missedIt;
  6:
  7:     // doesn't need to be volatile-doesn't change
  8:     private long creationTime;
  9:
 10:     public Volatile() {
 11:         value = 10;
 12:         missedIt = false;
 13:         creationTime = System.currentTimeMillis();
 14:     }
 15:
 16:     public void run() {
 17:         print("entering run()");
 18:
 19:         // each time, check to see if 'value'is different
 20:         while ( value < 20 ) {
 21:
 22:             // Used to break out of the loop if change to
 23:             // value is missed.
 24:             if  ( missedIt ) {
 25:                 int currValue = value;
 26:
 27:                 // Simply execute a synchronized statement on an
 28:                 // arbitrary object to see the effect.
 29:                 Object lock = new Object();
 30:                 synchronized ( lock ) {
 31:                     // do nothing!
 32:                 }
 33:
 34:                 int valueAfterSync = value;
 35:
 36:                 print("in run() - see value=" + currValue +
 37:                     ", but rumor has it that it changed!");
 38:                 print("in run() - valueAfterSync=" +
 39:                     valueAfterSync);
 40: 
 41:                 break;
 42:             }
 43:         }
 44:
 45:         print("leaving run()");
 46:     }
 47:
 48:     public void workMethod() throws InterruptedException {
 49:         print("entering workMethod()");
 50:
 51:         print("in workMethod() - about to sleep for 2 seconds");
 52:         Thread.sleep(2000);
 53:
 54:         value = 50;
 55:         print("in workMethod() - just set value=" + value);
 56:
 57:         print("in workMethod() - about to sleep for 5 seconds");
 58:         Thread.sleep(5000);
 59:
 60:         missedIt = true;
 61:         print("in workMethod() - just set missedIt=" + missedIt);
 62:
 63:         print("in workMethod() - about to sleep for 3 seconds");
 64:         Thread.sleep(3000);
 65:
 66:         print("leaving workMethod()");
 67:     }
 68:
 69:     private void print(String msg) {
 70:         // This method could have been simplified by using
 71:         // functionality present in the java.text package,
 72:         // but did not take advantage of it since that package
 73:         // is not present in JDK1.0.
 74:
 75:         long interval = System.currentTimeMillis() -
 76:                         creationTime;
 77:
 78:         String tmpStr = "    " + ( interval / 1000.0 ) + "000";
 79:        
 80:         int pos = tmpStr.indexOf(".");
 81:         String secStr = tmpStr.substring(pos - 2, pos + 4);
 82:
 83:         String nameStr = "        " +
 84:                 Thread.currentThread().getName();
 85: 
 86:         nameStr = nameStr.substring(nameStr.length() - 8,
 87:                                     nameStr.length());
 88:        
 89:         System.out.println(secStr + " " + nameStr + ": " + msg);
 90:     }
 91:
 92:     public static void main(String[] args) {
 93:         try {
 94:             Volatile vol = new Volatile();
 95:
 96:             // slight pause to let some time elapse
 97:             Thread.sleep(100); 
 98:
 99:             Thread t = new Thread(vol);
100:             t.start();
101:
102:             // slight pause to allow run() to go first
103:             Thread.sleep(100); 
104:
105:             vol.workMethod();
106:         } catch ( InterruptedException x ) {
107:             System.err.println(
108:                 "one of the sleeps was interrupted");
109:         }
110:     }
111: }

In the main() method of Volatile (lines 92–110), the main thread creates a new instance of Volatile (line 94). It then sleeps for a fraction of a second (line 97) to allow the real-time clock to advance slightly so that the messages created in print() print an elapsed time greater than zero. The main thread then creates a new Thread, passing in the Runnable instance of Volatile, and starts it (lines 99–100). Another brief nap is taken to allow the new thread to get into the loop of the run() method (line 103). The main thread is then used to execute the code in the workMethod() of the Volatile object (line 105).

Meanwhile, the new thread enters the run() method (lines 16–46). It continues to execute the while loop (lines 20–43) as long as the non-volatile member variable value (line 3) is less than 20. Earlier, when the main thread executed the constructor code (lines 10–14), value was initialized to 10. The only place that value is changed is inside workMethod() (line 54). In case the new thread doesn't see the change to value, the volatile member variable missedIt (line 5) is used to provide another way out. If missedIt is true (line 24), the new thread captures its perception of value into the local variable currValue (line 25). Then a new object is created simply to use as a lock (line 29). The thread locks on this object and enters and exits the synchronized block (lines 30–32). It then records its new perception of value into the local variable valueAfterSync (line 34). The values recorded before and after the synchronized statement are then printed (lines 36–39). The break statement (line 41) is used to get out of the while loop, print a message (line 45), and allow the thread to return from run().

While the new thread has been checking what it sees as the values of value and missedIt, the main thread has been slowly progressing through workMethod() (lines 48–67). After sleeping for two seconds (line 52), the main thread sets value to 50 (line 54). This is greater than 20 and should cause the thread inside run() to leave the while loop, but that thread might not see the change! After setting value, the main thread sleeps for five seconds to give the other thread a chance to notice the change (line 58). In case setting value to 50 was not noticed, it proceeds to set missedIt to true (line 60) and then sleeps for three seconds. Because value is not volatile and missedIt is volatile, you'll be able to see whether the use of volatile is important.

Listing 7.2 shows the possible output when Volatile is run. Your output should be a close match with only a couple of lines swapped.

Example 7.2. Possible Output from Volatile

 1:  0.170 Thread-0: entering run()
 2:  0.280     main: entering workMethod()
 3:  0.610     main: in workMethod() - about to sleep for 2 seconds
 4:  2.700     main: in workMethod() - just set value=50
 5:  2.700     main: in workMethod() - about to sleep for 5 seconds
 6:  7.750     main: in workMethod() - just set missedIt=true
 7:  7.750     main: in workMethod() - about to sleep for 3 seconds
 8:  7.750 Thread-0: in run() - see value=10, but rumor has it that it changed!
 9:  7.750 Thread-0: in run() - valueAfterSync=50
10:  7.750 Thread-0: leaving run()
11: 10.710     main: leaving workMethod()

Notice that 2.700 seconds after startup, the main thread sets value equal to 50 (line 4). Thread-0 does not notice this change in value. At 7.750 seconds, main sets the volatile variable missedIt to true (line 6). Thread-0 notices this change right away, but still sees value as 10 (line 8). After Thread-0 runs through the synchronized block, it finally sees that value is 50 (line 9).

Listing 7.2 shows the output when Volatile is run with the default command-line options in the VM included in Sun Microsystem's JDK 1.2. If the built-in Just-In-Time (JIT) compiler is turned off,

java -Djava.compiler=NONE Volatile

the code behaves as if value was declared to be volatile. Listing 7.3 shows the different output that this produces. Again, your output should match closely with only a message or two swapped.

Example 7.3. Possible Output from Volatile When the JIT Is Disabled

1:  0.110 Thread-0: entering run()
2:  0.220     main: entering workMethod()
3:  0.220     main: in workMethod() - about to sleep for 2 seconds
4:  2.200     main: in workMethod() - just set value=50
5:  2.200     main: in workMethod() - about to sleep for 5 seconds
6:  2.200 Thread-0: leaving run()
7:  7.200     main: in workMethod() - just set missedIt=true
8:  7.250     main: in workMethod() - about to sleep for 3 seconds
9: 10.220     main: leaving workMethod()

Notice that this time, when value is set to 50 by the main thread at 2.200 seconds (line 4), Thread-0 notices right away (line 6). In fact, the while loop of the run() method of Volatile exits normally (without the break) and simply prints the "leaving run()" message. With the JIT turned off, all threads read and write directly to shared memory and don't keep a private copy of variables. It is as if every member variable is declared to be volatile.

As Table 7.1 shows, volatile makes a difference only when JDK 1.1 or 1.2 is run with the Just-In-Time compiler turned on. JDK 1.0 did not include a JIT, so it can't be turned on or off. The JIT performs optimizations that make the use of volatile necessary.

Table 7.1. When the Use of volatile Makes a Difference

Version Command Line volatile Matters
JDK 1.2 java Volatile Yes
JDK 1.2 java -Djava.compiler= NONE Volatile No
JDK 1.1 java Volatile Yes
JDK 1.1 java -nojit Volatile No
JDK 1.0 java Volatile No

Before Sun included a JIT with their VMs, volatile did not make a difference. In addition, even with the JIT, every time that a thread enters or leaves a synchronized block, it reconciles its private copy of a variable with the shared copy. Blocks of synchronized code are scattered throughout the java.* class libraries, so a developer might not even be aware that the private copy has been reconciled. For example, System.out.println() contains a synchronized block, so using it to print out the current value in Volatile would keep the private copy up to date, and the volatile modifier would seem to have no effect. Many developers have written code that should have used the volatile modifier on some of the member variables. But because of at least one of these reasons, the missing volatile failed to make a critical difference.

Tip

Use volatile on member variables that can be accessed by two or more threads unless all the threads access the variables within synchronized blocks of code. If a member variable remains constant after construction (it is read-only), there is no need for it to be volatile.

The volatile modifier exists to request that the VM always access the shared copy of the variable. This is less efficient than allowing the VM to perform optimizations by keeping a private copy. You should use volatile only when it is necessary; overuse will unnecessarily slow the application's execution.

synchronized Method Modifier

The addition of the synchronized modifier to a method declaration ensures that only one thread is allowed inside the method at a time. This can be useful in keeping out other threads while the state of an object is temporarily inconsistent.

Two Threads Simultaneously in the Same Method of One Object

As BothInMethod (see Listing 7.4) shows, normally more than one thread is allowed to be inside a method at a time.

Example 7.4. BothInMethod.java—Shows That More Than One Thread Can Be Inside a Method

 1: public class BothInMethod extends Object {
 2:     private String objID;
 3:
 4:     public BothInMethod(String objID) {
 5:         this.objID = objID;
 6:     }
 7:
 8:     public void doStuff(int val) {
 9:         print("entering doStuff()");
10:         int num = val * 2 + objID.length();
11:         print("in doStuff() - local variable num=" + num);
12:
13:         // slow things down to make observations
14:         try { Thread.sleep(2000); } catch ( InterruptedException x ) { }
15:
16:         print("leaving doStuff()");
17:     }
18:
19:     public void print(String msg) {
20:         threadPrint("objID=" + objID + " - " + msg);
21:     }
22:
23:     public static void threadPrint(String msg) {
24:         String threadName = Thread.currentThread().getName();
25:         System.out.println(threadName + ": " + msg);
26:     }
27:
28:     public static void main(String[] args) {
29:         final BothInMethod bim = new BothInMethod("obj1");
30:
31:         Runnable runA = new Runnable() {
32:                 public void run() {
33:                     bim.doStuff(3);
34:                 }
35:             };
36:
37:         Thread threadA = new Thread(runA, "threadA");
38:         threadA.start();
39: 
40:         try { Thread.sleep(200); } catch ( InterruptedException x ) { }
41:
42:         Runnable runB = new Runnable() {
43:                 public void run() {
44:                     bim.doStuff(7);
45:                 }
46:             };
47:
48:         Thread threadB = new Thread(runB, "threadB");
49:         threadB.start();
50:     }
51: }

In main() (lines 28–50), one BothInMethod object is instantiated with an identifier of obj1 (line 29). Next, two threads are created to simultaneously access the doStuff() method. The first is named threadA, and the second threadB. After threadA is started (line 38), it invokes doStuff() and passes in 3 (line 33). About 200 milliseconds later, threadB is started (line 49) and invokes doStuff() on the same object, passing in 7 (line 44).

Both threadA and threadB will be inside doStuff() (lines 8–17) at the same time; threadA will enter first, and threadB will follow 200 milliseconds later. Inside doStuff(), the local variable num is calculated using the int passed in as parameter val and the member variable objID (line 10). Because threadA and threadB each pass in a different val, the local variable num will be different for each thread. A sleep is used inside doStuff() to slow things down enough to prove that both threads are simultaneously inside the same method of the same object (line 14).

Note

If two or more threads are simultaneously inside a method, each thread has its own copy of local variables.

Listing 7.5 shows the output produced when BothInMethod is run. Your output should match. Notice that threadA enters doStuff() first (line 1) and calculates that its local variable num is 10 (line 2). While threadA is still inside doStuff(), threadB also enters it (line 3) and calculates that its local variable num is 18 (line 4). Next, threadA reports that it is leaving doStuff() (line 5) and is followed closely by threadB (line 6).

Example 7.5. Output from BothInMethod (Your Output Should Match)

1: threadA: objID=obj1 - entering doStuff()
2: threadA: objID=obj1 - in doStuff() - local variable num=10
3: threadB: objID=obj1 - entering doStuff()
4: threadB: objID=obj1 - in doStuff() - local variable num=18
5: threadA: objID=obj1 - leaving doStuff()
6: threadB: objID=obj1 - leaving doStuff()

One Thread at a Time

More than one thread can be inside a method, and each thread keeps a copy of its own local variables. However, there are times when application constraints require that only one thread be permitted inside a method at a time. In OnlyOneInMethod (see Listing 7.6), the method modifier synchronized has been added to doStuff() (line 8). Other than this change, the rest of the class works the same as it did in BothInMethod.

Example 7.6. OnlyOneInMethod.java—Restricting Access to One Thread at a Time

 1: public class OnlyOneInMethod extends Object {
 2:     private String objID;
 3:
 4:     public OnlyOneInMethod(String objID) {
 5:         this.objID = objID;
 6:     }
 7:
 8:     public synchronized void doStuff(int val) {
 9:         print("entering doStuff()");
10:         int num = val * 2 + objID.length();
11:         print("in doStuff() - local variable num=" + num);
12:
13:         // slow things down to make observations
14:         try { Thread.sleep(2000); } catch ( InterruptedException x ) { }
15:
16:         print("leaving doStuff()");
17:     }
18:
19:     public void print(String msg) {
20:         threadPrint("objID=" + objID + " - " + msg);
21:     }
22:
23:     public static void threadPrint(String msg) {
24:         String threadName = Thread.currentThread().getName();
25:         System.out.println(threadName + ": " + msg);
26:     }
27:
28:     public static void main(String[] args) {
29:         final OnlyOneInMethod ooim = new OnlyOneInMethod("obj1");
30:
31:         Runnable runA = new Runnable() {
32:                 public void run() {
33:                     ooim.doStuff(3);
34:                 }
35:             };
36:
37:         Thread threadA = new Thread(runA, "threadA");
38:         threadA.start();
39: 
40:         try { Thread.sleep(200); } catch ( InterruptedException x ) { }
41:
42:         Runnable runB = new Runnable() {
43:                 public void run() {
44:                     ooim.doStuff(7);
45:                 }
46:             };
47:
48:         Thread threadB = new Thread(runB, "threadB");
49:         threadB.start();
50:     }
51: }

Only one thread at a time will be allowed into doStuff() because it is now synchronized. Listing 7.7 shows the output when OnlyOneInMethod is run. Your output should match. Notice that this time, threadA enters (line 1) and leaves (line 3) the doStuff() method before threadB is allowed to enter (line 4). The addition of the synchronized modifier successfully guarded doStuff() and allowed only one thread to enter at a time.

Example 7.7. Output from OnlyOneInMethod (Your Output Should Match)

1: threadA: objID=obj1 - entering doStuff()
2: threadA: objID=obj1 - in doStuff() - local variable num=10
3: threadA: objID=obj1 - leaving doStuff()
4: threadB: objID=obj1 - entering doStuff()
5: threadB: objID=obj1 - in doStuff() - local variable num=18
6: threadB: objID=obj1 - leaving doStuff()

When a thread encounters a synchronized instance method, it blocks until it can get exclusive access to the object-level mutex lock. Mutex is short for mutual exclusion. A mutex lock can be held by only one thread at a time. Other threads waiting for the lock will block until it is released. When the lock is released, all the threads waiting for it compete for exclusive access. Only one will be successful, and the other threads will go back into a blocked state waiting for the lock to be released again.

If one synchronized method on an object invokes another synchronized method on that same object, it will not block trying to get the object-level lock because it already has exclusive access to the lock.

Two Threads, Two Objects

Every instance of a class has its own object-level lock. The TwoObjects class shown in Listing 7.8 demonstrates that each object has its own object-level lock.

Example 7.8. TwoObjects.java

 1: public class TwoObjects extends Object {
 2:     private String objID;
 3:
 4:     public TwoObjects(String objID) {
 5:         this.objID = objID;
 6:     }
 7:
 8:     public synchronized void doStuff(int val) {
 9:         print("entering doStuff()");
10:         int num = val * 2 + objID.length();
11:         print("in doStuff() - local variable num=" + num);
12:
13:         // slow things down to make observations
14:         try { Thread.sleep(2000); } catch ( InterruptedException x ) { }
15:
16:         print("leaving doStuff()");
17:     }
18:
19:     public void print(String msg) {
20:         threadPrint("objID=" + objID + " - " + msg);
21:     }
22:
23:     public static void threadPrint(String msg) {
24:         String threadName = Thread.currentThread().getName();
25:         System.out.println(threadName + ": " + msg);
26:     }
27:
28:     public static void main(String[] args) {
29:         final TwoObjects obj1 = new TwoObjects("obj1");
30:         final TwoObjects obj2 = new TwoObjects("obj2");
31:
32:         Runnable runA = new Runnable() {
33:                 public void run() {
34:                     obj1.doStuff(3);
35:                 }
36:             };
37: 
38:         Thread threadA = new Thread(runA, "threadA");
39:         threadA.start();
40:
41:         try { Thread.sleep(200); } catch ( InterruptedException x ) { }
42:
43:         Runnable runB = new Runnable() {
44:                 public void run() {
45:                     obj2.doStuff(7);
46:                 }
47:             };
48:
49:         Thread threadB = new Thread(runB, "threadB");
50:         threadB.start();
51:     }
52: }

This time, two different objects are created (lines 29–30). The doStuff() method of obj1 is invoked by threadA (line 34). A fraction of a second later, the doStuff() method of obj2 is invoked by threadB (line 45). The doStuff() method is synchronized (line 8), but this time there is no competition between threadA and threadB for exclusive access. Each thread gets exclusive access to the object-level lock of the instance it is working on. Listing 7.9 shows the output when TwoObjects is run. Your output should match.

Example 7.9. Output from TwoObjects (Your Output Should Match)

1: threadA: objID=obj1 - entering doStuff()
2: threadA: objID=obj1 - in doStuff() - local variable num=10
3: threadB: objID=obj2 - entering doStuff()
4: threadB: objID=obj2 - in doStuff() - local variable num=18
5: threadA: objID=obj1 - leaving doStuff()
6: threadB: objID=obj2 - leaving doStuff()

Although the doStuff() method is synchronized, there is no competition for exclusive access to the object-level lock. Each instance, obj1 and obj2, has its own object-level lock. When threadA enters the doStuff() method of obj1 (line 1), it acquires exclusive access to the object-level lock for obj1. When threadB enters the doStuff() method of obj2 (line 3), it acquires exclusive access to the object-level lock for obj2.

Avoiding Accidental Corruption of an Object

CorruptWrite, shown in Listing 7.10, demonstrates the need to control concurrent access to a method. In this example, two strings are passed to a method for assignment into member variables. The trouble is that two threads are simultaneously trying to make assignments, and their assignments might get interleaved.

Example 7.10. CorruptWrite.java—Trouble Because of the Lack of Synchronization

 1: public class CorruptWrite extends Object {
 2:     private String fname;
 3:     private String lname;
 4:
 5:     public void setNames(String firstName, String lastName) {
 6:         print("entering setNames()");
 7:         fname = firstName;
 8:
 9:         // A thread might be swapped out here, and may stay
10:         // out for a varying amount of time. The different
11:         // sleep times exaggerate this.
12:         if ( fname.length() < 5 ) {
13:             try { Thread.sleep(1000); }
14:             catch ( InterruptedException x ) { }
15:         } else {
16:             try { Thread.sleep(2000); } 
17:             catch ( InterruptedException x ) { }
18:         }
19:
20:         lname = lastName;
21:
22:         print("leaving setNames() - " + lname + ", " + fname);
23:     }
24:
25:     public static void print(String msg) {
26:         String threadName = Thread.currentThread().getName();
27:         System.out.println(threadName + ": " + msg);
28:     }
29:
30:     public static void main(String[] args) {
31:         final CorruptWrite cw = new CorruptWrite();
32:
33:         Runnable runA = new Runnable() {
34:                 public void run() {
35:                     cw.setNames("George", "Washington");
36:                 }
37:             };
38:
39:         Thread threadA = new Thread(runA, "threadA");
40:         threadA.start();
41: 
42:         try { Thread.sleep(200); } 
43:         catch ( InterruptedException x ) { }
44:
45:         Runnable runB = new Runnable() {
46:                 public void run() {
47:                     cw.setNames("Abe", "Lincoln");
48:                 }
49:             };
50:
51:         Thread threadB = new Thread(runB, "threadB");
52:         threadB.start();
53:     }
54: }

In main() (lines 30–53), a single instance of CorruptWrite is created and referred to by cw (line 31). Two threads are started and both call the setNames() method passing in a first and last name. First, threadA executes cw.setNames("George", "Washington") (line 35) and 200 milliseconds later, threadB executes cw.setNames("Abe", "Lincoln") (line 47).

CorruptWrite has two member variables, fname and lname (lines 2–3), that hold the names passed to setNames(). Inside setNames() (lines 5–23), the first parameter passed is assigned to fname (line 7). Then, depending on the length of fname, the thread sleeps for either one or two seconds (lines 12–18). After the nap, the second parameter is assigned to the member variable lname (line 20).

I used the variable length sleep inside setNames() to exaggerate what might really happen. The exaggeration will help to land the object in an inconsistent state. Real-world code would look more like the following:

public void setNames(String firstName, String lastName) {
   fname = firstName;
   lname = lastName;
}

This revised and quick setNames() method is still subtly dangerous in a multithreaded environment. It is possible that threadA could be swapped off the processor by the thread scheduler just after making the fname assignment, and just before making the lname assignment. Although threadA is halfway complete with its work, threadB could come along and assign its parameters to both fname and lname. When threadA gets scheduled to run again, it finishes up by assigning its second parameter to lname. This leaves the object in an inconsistent state. Most of the time this code will run fine, but now and then it will corrupt the object. By adding the variable-length sleeps in the setNames() method used in CorruptWrite, I have guaranteed that the object will wind up corrupted for the purposes of demonstration. Table 7.2 summarizes the states of the object at various points in time.

Table 7.2. States of the Member Variables of CorruptWrite at Various Points in Time

fname lname Point in Time
null null Before either thread enters setNames()
George null After threadA sets fname
Abe Lincoln After threadB sets both
Abe Washington After threadA sets lname

Listing 7.11 shows the output produced when CorruptWrite is run. Your output should match. Before going to sleep, threadA reports that it has entered the setNames() method (line 1). While threadA is sleeping, threadB comes along and enters setNames() (line 2). It assigns values to both member variables and just before returning, prints their values (line 3). When threadA wakes up, it assigns its second value and just before returning, prints the current values of the member variables (line 4) showing the inconsistent state. The name-pair "Abe" and "Washington" was never passed to setNames(), but this is the current (corrupted) state of the object.

Example 7.11. Output from CorruptWrite (Your Output Should Match)

1: threadA: entering setNames()
2: threadB: entering setNames()
3: threadB: leaving setNames() - Lincoln, Abe
4: threadA: leaving setNames() - Washington, Abe

FixedWrite (see Listing 7.12) corrects the dangerous code in CorruptWrite simply by adding the synchronized method modifier to the setNames() method (line 5). Otherwise, FixedWrite is basically the same as CorruptWrite.

Example 7.12. FixedWrite.java—Using synchronized to Control Concurrent Changes

 1: public class FixedWrite extends Object {
 2:     private String fname;
 3:     private String lname;
 4:
 5:     public synchronized void setNames(
 6:                 String firstName,
 7:                 String lastName
 8:             ) {
 9:
10:         print("entering setNames()");
11:         fname = firstName;
12:
13:         // A thread might be swapped out here, and may stay
14:         // out for a varying amount of time. The different
15:         // sleep times exaggerate this.
16:         if ( fname.length() < 5 ) {
17:             try { Thread.sleep(1000); } 
18:             catch ( InterruptedException x ) { }
19:         } else {
20:             try { Thread.sleep(2000); }
21:             catch ( InterruptedException x ) { }
22:         }
23:
24:         lname = lastName;
25:
26:         print("leaving setNames() - " + lname + ", " + fname);
27:     }
28:
29:     public static void print(String msg) {
30:         String threadName = Thread.currentThread().getName();
31:         System.out.println(threadName + ": " + msg);
32:     }
33:
34:     public static void main(String[] args) {
35:         final FixedWrite fw = new FixedWrite();
36:
37:         Runnable runA = new Runnable() {
38:                 public void run() {
39:                     fw.setNames("George", "Washington");
40:                 }
41:             };
42:
43:         Thread threadA = new Thread(runA, "threadA");
44:         threadA.start();
45:
46:         try { Thread.sleep(200); } 
47:         catch ( InterruptedException x ) { }
48:
49:         Runnable runB = new Runnable() {
50:                 public void run() {
51:                     fw.setNames("Abe", "Lincoln");
52:                 }
53:             };
54:
55:         Thread threadB = new Thread(runB, "threadB");
56:         threadB.start();
57:     }
58: }

If you make setNames() a sychronized method, each thread that tries to enter this method will block until it can get exclusive access to the object-level lock. Now when threadA goes to sleep (or if the thread scheduler had otherwise decided to swap it out), threadB is prevented from entering the method. When threadA finally completes its work inside setNames(), it automatically releases the lock as it returns. Now, threadB is able to gain exclusive access to the lock and enters setNames().

The variable-length sleeps are still inside setNames() to demonstrate that the addition of the synchronized method modifier solved the problem. Real-world code (that is truly safe) would look more like the following:

public synchronized void setNames(String firstName, String lastName) {
   fname = firstName;
   lname = lastName;
}

Listing 7.13 shows the output when FixedWrite is run. Your output should match. Notice that threadB doesn't enter setNames() (line 3) until threadA has left it (line 2). In this fixed version, both threadA and threadB print consistent name-pairs when they leave (lines 2, 4).

Example 7.13. Output from FixedWrite (Your Output Should Match)

1: threadA: entering setNames()
2: threadA: leaving setNames() - Washington, George
3: threadB: entering setNames()
4: threadB: leaving setNames() - Lincoln, Abe

Deferring Access to an Object While It Is Inconsistent

FixedWrite solved the problem by ensuring that the object was left in a consistent state by a call to setNames(). The object is consistent both before and after a thread invokes setNames(), but it is inconsistent while a thread is inside setNames(). If one thread is executing setNames() at the same time that a second thread is accessing the member variables, the second thread might occasionally see an inconsistent name-pair. DirtyRead (see Listing 7.14) demonstrates this problem.

Example 7.14. DirtyRead.java—Accessing Members Variables While in an Inconsistent State

 1: public class DirtyRead extends Object {
 2:     private String fname;
 3:     private String lname;
 4:
 5:     public String getNames() {
 6:         return lname + ", " + fname;
 7:     }
 8:
 9:     public synchronized void setNames(
10:                 String firstName,
11:                 String lastName
12:             ) {
13:
14:         print("entering setNames()");
15:         fname = firstName;
16:
17:         try { Thread.sleep(1000); } 
18:         catch ( InterruptedException x ) { }
19:
20:         lname = lastName;
21:         print("leaving setNames() - " + lname + ", " + fname);
22:     }
23:
24:     public static void print(String msg) {
25:         String threadName = Thread.currentThread().getName();
26:         System.out.println(threadName + ": " + msg);
27:     }
28:
29:     public static void main(String[] args) {
30:         final DirtyRead dr = new DirtyRead();
31:         dr.setNames("George", "Washington"); // initially
32:
33:         Runnable runA = new Runnable() {
34:                 public void run() {
35:                     dr.setNames("Abe", "Lincoln");
36:                 }
37:             };
38:
39:         Thread threadA = new Thread(runA, "threadA");
40:         threadA.start();
41: 
42:         try { Thread.sleep(200); } 
43:         catch ( InterruptedException x ) { }
44:
45:         Runnable runB = new Runnable() {
46:                 public void run() {
47:                     print("getNames()=" + dr.getNames());
48:                 }
49:             };
50:
51:         Thread threadB = new Thread(runB, "threadB");
52:         threadB.start();
53:     }
54: }

DirtyRead is an evolution of FixedWrite. A new method called getNames() has been added (lines 5–7). This method constructs and returns a new String that is a combination of the lname and fname member variables (line 6).

The setNames() method (lines 9–22) is still synchronized (line 9). It has been simplified to just sleep for one second between the setting of the first name and last name.

Inside main() (lines 29–53), a few different actions are taken. First, just after the DirtyRead instance is created (line 30), setNames() is invoked by the main thread to initially set the names to "George" and "Washington" (line 31). After that, threadA invokes setNames(), passing in "Abe" and "Lincoln" (line 35). threadA runs for about 200 milliseconds before threadB is started (line 42).

The setNames() method is still slow, so while the names are in the process of being changed by threadA, threadB invokes the getNames() method (line 47). Listing 7.15 shows the output when DirtyRead is run. Your output should match.

Example 7.15. Output from DirtyRead (Your Output Should Match)

1: main: entering setNames()
2: main: leaving setNames() - Washington, George
3: threadA: entering setNames()
4: threadB: getNames()=Washington, Abe
5: threadA: leaving setNames() - Lincoln, Abe

The setNames() method is invoked by threadA (line 3). Before it can finish setting both names, threadB invokes getNames() (line 4). It returns the combination of "Abe" and "Washington," catching the object in an inconsistent state. When threadA finishes executing setNames(), the object is back to a consistent state (line 5).

It's an unavoidable fact that the object must be in an inconsistent state for a brief period of time, even with everything except the assignments taken out:

public synchronized void setNames(String firstName, String lastName) {
   fname = firstName;
   lname = lastName;
}

No matter how fast the processor is, it's possible that the thread scheduler could swap out the thread making the changes after it has changed fname but before it has changed lname. Holding an object-level lock does not prevent a thread from being swapped out. And if it is swapped out, it continues to hold the object-level lock. Because of this, care must be taken to ensure that all reads are blocked when the data is in an inconsistent state. CleanRead (see Listing 7.16) simply adds the synchronized method modifier to getNames() to control concurrent reading and writing.

Example 7.16. CleanRead.java—Using synchronized to Control Concurrent Access While Changes Are Being Made

 1: public class CleanRead extends Object {
 2:     private String fname;
 3:     private String lname;
 4:
 5:     public synchronized String getNames() {
 6:         return lname + ", " + fname;
 7:     }
 8:
 9:     public synchronized void setNames(
10:                 String firstName,
11:                 String lastName
12:             ) {
13:
14:         print("entering setNames()");
15:         fname = firstName;
16:
17:         try { Thread.sleep(1000); } 
18:         catch ( InterruptedException x ) { }
19:
20:         lname = lastName;
21:         print("leaving setNames() - " + lname + ", " + fname);
22:     }
23:
24:     public static void print(String msg) {
25:         String threadName = Thread.currentThread().getName();
26:         System.out.println(threadName + ": " + msg);
27:     }
28:
29:     public static void main(String[] args) {
30:         final CleanRead cr = new CleanRead();
31:         cr.setNames("George", "Washington"); // initially
32:
33:         Runnable runA = new Runnable() {
34:                 public void run() {
35:                     cr.setNames("Abe", "Lincoln");
36:                 }
37:             };
38:
39:         Thread threadA = new Thread(runA, "threadA");
40:         threadA.start();
41: 
42:         try { Thread.sleep(200); } 
43:         catch ( InterruptedException x ) { }
44:
45:         Runnable runB = new Runnable() {
46:                 public void run() {
47:                     print("getNames()=" + cr.getNames());
48:                 }
49:             };
50:
51:         Thread threadB = new Thread(runB, "threadB");
52:         threadB.start();
53:     }
54: }

Inside main() (lines 29–53), the same actions are taken as were for DirtyRead. First, just after the CleanRead instance is created (line 30), setNames() is invoked by the main thread to initially set the names to "George" and "Washington" (line 31). After that, threadA invokes setNames(), passing in "Abe" and "Lincoln" (line 35). threadA runs for about 200 milliseconds before threadB is started (line 42).

The setNames() method is still slow, so while the names are in the process of being changed by threadA, threadB invokes the getNames() method (line 47). Because getNames() is now synchronized, threadB blocks trying to get exclusive access to the object-level lock. When threadA returns from setNames(), it automatically releases the object-level lock. Then threadB proceeds to acquire the object-level lock and enters the getNames() method. Listing 7.17 shows the output when CleanRead is run. Your output should match.

Example 7.17. Output from CleanRead (Your Output Should Match)

1: main: entering setNames()
2: main: leaving setNames() - Washington, George
3: threadA: entering setNames()
4: threadA: leaving setNames() - Lincoln, Abe
5: threadB: getNames()=Lincoln, Abe

Although threadB invokes getNames() before threadA is finished with setNames(), it blocks waiting to get exclusive access to the object-level lock. This time, getNames() returns a valid name-pair (line 5).

Tip

If two or more threads might be simultaneously interacting with the member variables of an object, and at least one of those threads might change the values, it is generally a good idea to use synchronized to control concurrent access. If only one thread will be accessing an object, using synchronized is unnecessary and slows execution.

synchronized Statement Block

The synchronized block can be used when a whole method does not need to be synchronized or when you want the thread to get an object-level lock on a different object. The synchronized statement block looks like this:

synchronized ( obj ) {
    // block of code
}

where obj is a reference to the object whose object-level lock must be acquired before entering the block of code.

This setPoint() method

public synchronized void setPoint(int x, int y) {
    this.x = x;
    this.y = y;
}

can be rewritten to instead use a synchronized block:

public void setPoint(int x, int y) {
    synchronized ( this ) {
        this.x = x;
        this.y = y;
    }
}

The behavior of both versions of setPoint() is virtually the same. They do compile to different byte-code, but both of them make sure that they have exclusive access to the object-level lock for the instance before making changes to x and y.

Reducing the Time That the Lock Is Held

A synchronized block can be used to reduce the time that the object-level lock is held. If a method does a lot of other things that don't require access to the member variables, it can shorten the time that it holds the lock to just the critical portion:

public void setValues(int x, double ratio) {
    // Some other, long-running statements that don't work
    // with the member variables go here.
    // ...
    double processedValA = // ... long calculation ...
    double processedValB = // ... long calculation ...
    // ...
    synchronized ( this ) {
        a = processedValA;
        b = processedValB;
    }
}

In setValues(), exclusive access to the object-level lock is not needed until the time-consuming calculations have been made and the results are ready to be stored. At the bottom of the method, the object-level lock is acquired and held briefly to simply assign new values to the member variables a and b.

Locking an Object Other Than this

When a synchronized statement is used as follows,

synchronized ( mutex ) {
    // block of code
}

the reference mutex indicates the object whose object-level lock must be acquired before entering the statement block. It can be a reference to any object in the VM, not just this. Regardless of how a thread leaves a synchronized block, it automatically releases the lock. This includes a return statement, a throw statement, or just falling through to the next statement after the block. Calling a method from within the synchronized block does not constitute leaving the block (the lock is still held).

Sometimes you will need to call two synchronized methods on an object and be sure that no other thread sneaks in between the calls. Consider this code fragment from a class called Bucket:

public class Bucket extends Object {
    // ...
    public synchronized boolean isSpaceAvailable() { // ...
    public synchronized void add(BucketItem o)
                         throws NoSpaceAvailableException { // ...
    public synchronized BucketItem remove() { // ...
    // ...
}

All three methods use the synchronized modifier to control concurrent access by multiple threads. Items of the type BucketItem can be added and removed from Bucket. When using the object, a call should be made to isSpaceAvailable() before trying to add another BucketItem, to prevent the NoSpaceAvailableException from being thrown. Of course, there are other ways to design Bucket, but imagine that you are stuck with this class as is. To add an item to Bucket, the following code fragment could be used:

Bucket b = // ...
// ...
if ( b.isSpaceAvailable() ) {
    b.add(item);
}

This is fine if only one thread is interacting with this instance of Bucket. But if multiple threads are potentially trying to add BucketItem objects to the same Bucket, a new approach has to be taken to avoid a race condition. Imagine that threadA checks and sees that space is available, but before it actually adds its item, threadB checks and also sees that space is available. Now threadA and threadB are racing to actually add an item. Only one can win the race, and that thread gets to add its item. The other thread will fail to add its item and will throw a NoSpaceAvailableException. To prevent this problem, a synchronized block should be wrapped around the two method calls:

Bucket b = // ...
// ...
synchronized ( b ) {
    if ( b.isSpaceAvailable() ) {
        b.add(item);
    }
}

The synchronized block uses the object-level lock on b, the Bucket instance. This is the same lock that must be acquired before entering the isSpaceAvailable() and add() methods. If a thread can get the object-level lock and enter the synchronized block, it is guaranteed to be able to invoke isSpaceAvailable() and add() without blocking. Because it already has the object-level lock for b, there is no delay or competition to enter the synchronized methods. In addition, no other thread can invoke these methods until the first thread leaves the synchronized block.

Imagine that threadA is able to get exclusive access to the object-level lock on b and enters the synchronized block. It checks to see if space is available, but before it actually adds its item, the thread scheduler swaps it out. threadB comes along and is blocked from acquiring the lock on b, so it is swapped out. Regardless of any particular thread scheduling, no other thread will be able to get the object-level lock on b until threadA releases it. When threadA is finally scheduled to run again, it will proceed to add its element to b and release the lock as it leaves the synchronized block. After threadA leaves the synchronized block, threadB can acquire the object-level lock on b.

Safely Copying the Contents of a Vector into an Array

The Vector class is used to hold an expandable array of objects. Some of its methods are shown following:

public final synchronized void addElement(Object o)
public final synchronized void insertElementAt(Object o, int pos)
public final synchronized void setElementAt(Object o, int pos)
public final synchronized void removeElementAt(int pos)
public final synchronized boolean removeElement(Object o)
public final synchronized void removeAllElements()
public final int size()
public final synchronized Object elementAt(int pos)
public final synchronized Object firstElement()
public final synchronized Object lastElement()
public final synchronized Enumeration elements()
public final boolean contains(Object obj)
public final int indexOf(Object obj)

I generated this information using reflection on the Vector class in JDK 1.1 (information on 1.2 and Collections is included later in this chapter). Notice that many of the methods are synchronized—particularly the ones used to add and remove elements from the Vector.

SafeVectorCopy in Listing 7.18 shows how a synchronized block can be used in conjunction with the synchronized methods of Vector to copy the current contents of the Vector into a String[].

Example 7.18. SafeVectorCopy.java—Safely Copying the Contents of a Vector into an Array

 1: import java.util.*;
 2:
 3: public class SafeVectorCopy extends Object {
 4:     public static void main(String[] args) {
 5:         Vector vect = new Vector();
 6:         vect.addElement("Synchronization");
 7:         vect.addElement("is");
 8:         vect.addElement("important");
 9:
10:         String[] word;
11:
12:         synchronized ( vect ) {
13:             int size = vect.size();
14:             word = new String[size];
15:
16:             for ( int i = 0; i < word.length; i++ ) {
17:                 word[i] = (String) vect.elementAt(i);
18:             }
19:         }
20:
21:         System.out.println("word.length=" + word.length);
22:         for ( int i = 0; i < word.length; i++ ) {
23:             System.out.println("word[" + i + "]=" + word[i]);
24:         }
25:     }
26: }

In SafeVectorCopy, three strings are added to a new Vector referred to by vect (lines 5–8). The synchronized block (lines 12–19) uses the object-level lock of vect to keep out other threads while the copying is taking place. Inside the block, the current number of elements in the Vector is used to allocate a new String[] (lines 13–14). One by one, each element is retrieved, cast into a String, and stored into the array (lines 16–18). If another thread had been trying to access vect to add or remove elements during this time, it would have been blocked until the thread doing the copying left the synchronized block.

Caution

As of JDK 1.2, Vector and Hashtable have been incorporated into the Collections API. All the old methods are still present, but some new ones have been added that are not synchronized. The SafeVectorCopy example is only really safe for pre-1.2 code. Later in this chapter, I'll show you how to safely use Collections in a multithreaded environment.

static synchronized Methods

In addition to the object-level lock that exists for each instance of a class, there is a class-level lock that all instances of a particular class share. Every class loaded by the VM has exactly one class-level lock. If a method is both static and synchronized, a thread must get exclusive access to the class-level lock before entering the method.

The class-level lock can be used to control concurrent access to static member variables. Just as the object-level lock was needed to prevent data corruption in non-static member variables, the class-level lock is needed to prevent corruption of static member variables. Even when no variables are involved, the synchronized modifier can be used on static methods simply to ensure that only one thread is inside the method at a time.

As you might guess from the name, the StaticNeedSync class in Listing 7.19 demonstrates a case where a static method would benefit from the addition of the synchronized modifier.

Example 7.19. StaticNeedSync.java—Demonstrating the Need for Static Synchronized Methods

 1: public class StaticNeedSync extends Object {
 2:     private static int nextSerialNum = 10001;
 3:
 4:     public static int getNextSerialNum() {
 5:         int sn = nextSerialNum;
 6:
 7:         // Simulate a delay that is possible if the thread
 8:         // scheduler chooses to swap this thread off the
 9:         // processor at this point. The delay is exaggerated
10:         // for demonstration purposes.
11:         try { Thread.sleep(1000); } 
12:         catch ( InterruptedException x ) { }
13:
14:         nextSerialNum++;
15:         return sn;
16:     }
17:
18:     private static void print(String msg) {
19:         String threadName = Thread.currentThread().getName();
20:         System.out.println(threadName + ": " + msg);
21:     }
22:
23:     public static void main(String[] args) {
24:         try {
25:             Runnable r = new Runnable() {
26:                     public void run() {
27:                         print("getNextSerialNum()=" +
28:                                 getNextSerialNum());
29:                     }
30:                 };
31:            
32:             Thread threadA = new Thread(r, "threadA");
33:             threadA.start();
34:    
35:             Thread.sleep(1500);
36:    
37:             Thread threadB = new Thread(r, "threadB");
38:             threadB.start();
39:    
40:             Thread.sleep(500);
41: 
42:             Thread threadC = new Thread(r, "threadC");
43:             threadC.start();
44:    
45:             Thread.sleep(2500);
46:
47:             Thread threadD = new Thread(r, "threadD");
48:             threadD.start();
49:         } catch ( InterruptedException x ) {
50:             // ignore
51:         }
52:     }
53: }

The StaticNeedSync class has a private, static member variable, nextSerialNum, which is used to hold the next serial number that will be given out (line 2). The getNextSerialNum()method (lines 4–16) is both public and static. It is invoked when a unique serial number is needed. When called, it takes the current value of nextSerialNum and stores it in a local variable sn (line 5). Then, it puts the calling thread to sleep for one second to simulate the possibility that it could get swapped out at this point by the thread scheduler (line 11). When the thread gets a chance to run again, it increments the nextSerialNum member variable to prepare it for the next call (line 14). The locally stored serial number sn is returned to the caller (line 15).

The main thread starts four threads to interact with the getNextSerialNum() method. All four threads use the same Runnable (lines 25–30). Inside this Runnable, the results of calling getNextSerialNum() are printed along with the thread name (lines 27–28).

The main thread starts threadA (line 33) and then sleeps for 1.5 seconds. This is enough time for threadA to enter and return from getNextSerialNum(). Next, the main thread starts threadB (line 38) and then sleeps for 0.5 seconds (line 40) before starting threadC (line 43). Both threadB and threadC will be inside getNextSerialNum() at the same time—and this will cause some trouble.

After waiting 2.5 seconds (plenty of time for threadB and threadC to return), main starts threadD (lines 45–48). threadD invokes getNextSerialNum() one last time. Listing 7.20 shows the output produced when StaticNeedSync is run. Your output should match.

Example 7.20. Output from StaticNeedSync (Your Output Should Match)

threadA: getNextSerialNum()=10001
threadB: getNextSerialNum()=10002
threadC: getNextSerialNum()=10002
threadD: getNextSerialNum()=10004

When both threadB and threadC are allowed inside getNextSerialNum() at the same time, they both see 10002 for their number. Then each thread proceeds to increment the counter, and threadD sees 10004. Two callers get the same serial number, and no one gets 10003.

This code (without the sleep) is still vulnerable to duplicate serial numbers:

public static int getNextSerialNum() { // still dangerous
    int sn = nextSerialNum;
    nextSerialNum++;
    return sn;
}

Even this code has risks:

public static int getNextSerialNum() { // still dangerous
    return nextSerialNum++;
}

In both cases, a thread could be swapped out after reading the value of nextSerialNum but before incrementing it. This will happen only on rare occasions, but the risk should be eliminated.

StaticSync (see Listing 7.21) solves the problem by adding the synchronized method modifier (line 4) to the static method getNextSerialNum(). This ensures that only one thread is allowed into the method at a time and eliminates the problems of duplicate serial numbers.

Example 7.21. StaticSync.java—Using synchronized with static to Control Concurrent Access

 1: public class StaticSync extends Object {
 2:     private static int nextSerialNum = 10001;
 3:
 4:     public static synchronized int getNextSerialNum() {
 5:         int sn = nextSerialNum;
 6:
 7:         // Simulate a delay that is possible if the thread
 8:         // scheduler chooses to swap this thread off the
 9:         // processor at this point. The delay is exaggerated
10:         // for demonstration purposes.
11:         try { Thread.sleep(1000); } 
12:         catch ( InterruptedException x ) { }
13:
14:         nextSerialNum++;
15:         return sn;
16:     }
17:
18:     private static void print(String msg) {
19:         String threadName = Thread.currentThread().getName();
20:         System.out.println(threadName + ": " + msg);
21:     }
22:
23:     public static void main(String[] args) {
24:         try {
25:             Runnable r = new Runnable() {
26:                     public void run() {
27:                         print("getNextSerialNum()=" +
28:                                 getNextSerialNum());
29:                     }
30:                 };
31:            
32:             Thread threadA = new Thread(r, "threadA");
33:             threadA.start();
34:    
35:             Thread.sleep(1500);
36:    
37:             Thread threadB = new Thread(r, "threadB");
38:             threadB.start();
39:    
40:             Thread.sleep(500);
41: 
42:             Thread threadC = new Thread(r, "threadC");
43:             threadC.start();
44:    
45:             Thread.sleep(2500);
46:
47:             Thread threadD = new Thread(r, "threadD");
48:             threadD.start();
49:         } catch ( InterruptedException x ) {
50:             // ignore
51:         }
52:     }
53: }

Listing 7.22 shows the output produced when StaticSync is run. Your output should match.

Example 7.22. Output from StaticSync (Your Output Should Match)

threadA: getNextSerialNum()=10001
threadB: getNextSerialNum()=10002
threadC: getNextSerialNum()=10003
threadD: getNextSerialNum()=10004

Note that this time, threadC gets the unique serial number of 10003. The addition of synchronized to the static method getNextSerialNum() solves the problem by blocking threadC from entering until threadB was finished.

Using the Class-Level Lock in a synchronized Statement

The synchronized statement can also use a class-level lock. This can be useful if a static method runs for a long period of time. Additionally, it can be used to ensure that two static method calls by one thread are not interleaved with a call by another thread.

To lock on the class-level lock, use the following code

synchronized ( ClassName. class ) {
    // body
}

where ClassName is replaced with the real name of the class you want to use. Class StaticBlock (see Listing 7.23) demonstrates this technique.

Example 7.23. StaticBlock.java—Using .class to Gain Access to the Class-Level Lock

 1: public class StaticBlock extends Object {
 2:     public static synchronized void staticA() {
 3:         System.out.println("entering staticA()");
 4:
 5:         try { Thread.sleep(5000); } 
 6:         catch ( InterruptedException x ) { }
 7:
 8:         System.out.println("leaving staticA()");
 9:     }
10:
11:     public static void staticB() {
12:         System.out.println("entering staticB()");
13:
14:         synchronized ( StaticBlock.class ) {
15:             System.out.println(
16:                     "in staticB() - inside sync block");
17:
18:             try { Thread.sleep(2000); } 
19:             catch ( InterruptedException x ) { }
20:         }
21:
22:         System.out.println("leaving staticB()");
23:     }
24:
25:     public static void main(String[] args) {
26:         Runnable runA = new Runnable() {
27:                 public void run() {
28:                     StaticBlock.staticA();
29:                 }
30:             };
31:
32:         Thread threadA = new Thread(runA, "threadA");
33:         threadA.start();
34:
35:         try { Thread.sleep(200); } 
36:         catch ( InterruptedException x ) { }
37: 
38:         Runnable runB = new Runnable() {
39:                 public void run() {
40:                     StaticBlock.staticB();
41:                 }
42:             };
43:
44:         Thread threadB = new Thread(runB, "threadB");
45:         threadB.start();
46:     }
47: }

In StaticBlock, the staticA() method (lines 2–9) is both static and synchronized. The staticB() method (lines 11–23) is static and contains a synchronized block (lines 14–20). The object used to control access to this block is the Class object for StaticBlock and is found by using StaticBlock.class (line 14).

In main() (lines 25–46), threadA is started and invokes staticA() (line 28). After a brief 200 millisecond break, threadB is started and invokes staticB(). While threadA is sleeping inside staticA(), threadB enters staticB(), prints a message, and blocks waiting to enter the synchronized statement block (line 14). When threadA returns from staticA(), threadB is able to get the class-level lock and completes staticB().

Listing 7.24 shows the output produced when StaticBlock is run. Your output should match.

Example 7.24. Output from StaticBlock (Your Output Should Match)

1: entering staticA()
2: entering staticB()
3: leaving staticA()
4: in staticB() - inside sync block
5: leaving staticB()

Notice that although threadB is able to enter staticB() (line 2), it can't enter the synchronized block (line 4) until threadA returns from staticA() (line 3). threadB blocks waiting for threadA to release the class-level lock.

Synchronization and the Collections API

The Collections API is new to JDK 1.2 and includes a number of interfaces and classes that facilitate the manipulation of collections of objects. Vector and Hashtable are now part of the Collections API, but have retained their old functionality for backward compatibility. Some of the new classes and interfaces include Collection, List, Map, Set, ArrayList, LinkedList, and HashMap.

Wrapping Collections to Make Them synchronized

Vector and Hashtable were originally designed to be multithread-safe. Take Vector, for example—the methods used to add and remove elements are synchronized. If only one thread will ever interact with an instance of Vector, the work required to acquire and release the object-level lock is wasted.

The designers of the Collections API wanted to avoid the overhead of synchronization when it wasn't necessary. As a result, none of the methods that alter the contents of a collection are synchronized. If a Collection or Map will be accessed by multiple threads, it should be wrapped by a class that synchronizes all the methods.

Caution

Collections are not inherently multithread-safe. Extra steps must be taken when more than one thread will be interacting with a collection to make it multithread-safe.

There are several static methods in the Collections class that are used to wrap unsynchronized collections with synchronized methods:

public static Collection synchronizedCollection(Collection c)
public static List synchronizedList(List l)
public static Map synchronizedMap(Map m)
public static Set synchronizedSet(Set s)
public static SortedMap synchronizedSortedMap(SortedMap sm)
public static SortedSet synchronizedSortedSet(SortedSet ss)

Basically, these methods return new classes that have synchronized versions of the collections'methods. To create a List that is multithread-safe and backed by an ArrayList, use the following:

List list = Collections.synchronizedList(new ArrayList());

Notice that the ArrayList instance was immediately wrapped and that no direct reference to the unsynchronized ArrayList exists. This is the safest approach. If another thread was to get a direct reference to the ArrayList instance, it could perform unsynchronized changes.

Tip

When synchronizing collections, do not keep any direct reference to the original unsynchronized collection. This will ensure that no other thread accidentally makes uncoordinated changes.

Safely Copying the Contents of a List into an Array

The SafeListCopy class in Listing 7.25 shows three different ways that the contents of a List can be safely copied into a String[] in a multithreaded environment.

Example 7.25. SafeListCopy.java—Safely Copying the Contents of a List into an Array

 1: import java.util.*;
 2:
 3: public class SafeListCopy extends Object {
 4:     public static void printWords(String[] word) {
 5:         System.out.println("word.length=" + word.length);
 6:         for ( int i = 0; i < word.length; i++ ) {
 7:             System.out.println("word[" + i + "]=" + word[i]);
 8:         }
 9:     }
10:
11:     public static void main(String[] args) {
12:         // To be safe, only keep a reference to the
13:         // *synchronized* list so that you are sure that
14:         // all accesses are controlled.
15:         List wordList =
16:                 Collections.synchronizedList(new ArrayList());
17:
18:         wordList.add("Synchronization");
19:         wordList.add("is");
20:         wordList.add("important");
21:
22:         // First technique (favorite)
23:         String[] wordA =
24:                 (String[]) wordList.toArray(new String[0]);
25:
26:         printWords(wordA);
27:
28:         // Second technique
29:         String[] wordB;
30:         synchronized ( wordList ) {
31:             int size = wordList.size();
32:             wordB = new String[size];
33:             wordList.toArray(wordB);
34:         }
35:
36:         printWords(wordB);
37:
38:         // Third technique (the 'synchronized'*is* necessary)
39:         String[] wordC;
40:         synchronized ( wordList ) {
41:             wordC = (String[]) wordList.toArray(
42:                                 new String[wordList.size()]);
43:         }
44: 
45:         printWords(wordC);
46:     }
47: }

In SafeListCopy, a multithread-safe version of List is constructed using Collections.synchronizedList() and is backed by an ArrayList instance (lines 15–16). Three Strings are added to the List (lines 18–20).

The first technique for copying the contents of wordList into a String[] is the simplest and exploits a convenient feature of collections. The toArray() method is used to copy the contents into an array of the type specified by the parameter (lines 23–24). Because the toArray() method was synchronized by wrapping, this is all that is necessary to safely copy the contents. If other threads were trying to add or remove elements during this operation, they would be blocked until it completed.

The second technique uses a synchronized statement to keep other threads out while two steps are performed (lines 29–34). The first step is to determine the exact length needed for the destination String[] and to allocate the array (lines 31–32). The second step is to pass this perfectly sized array to the toArray() method (line 33). This second technique has a slight efficiency advantage over the first one in that it does not create a throw-away, zero-length String[].

The third technique combines the steps of the second technique into one line (lines 39–43). It still needs to be inside a synchronized block because another thread could intervene after the size() method is called but before the toArray() method is called (line 42).

I would recommend that you stick with the first technique in most cases because it is the most straightforward and least error-prone. Listing 7.26 shows the output when SafeListCopy is run. Your output should match. Notice that in all three cases the proper set of strings is produced.

Example 7.26. Output from SafeListCopy (Your Output Should Match)

word.length=3
word[0]=Synchronization
word[1]=is
word[2]=important
word.length=3
word[0]=Synchronization
word[1]=is
word[2]=important
word.length=3
word[0]=Synchronization
word[1]=is
word[2]=important

Safely Iterating Through the Elements of a Collection

The elements of a Collection can be stepped through one by one by using an Iterator. In a multithreaded environment, you will generally want to block other threads from adding or removing elements while you are iterating through the current collection of elements.

The class SafeCollectionIteration shown in Listing 7.27 demonstrates how to block other threads while using an Iterator.

Example 7.27. SafeCollectionIteration.java—Safely Iterating Through the Elements of a Collection

 1: import java.util.*;
 2:
 3: public class SafeCollectionIteration extends Object {
 4:     public static void main(String[] args) {
 5:         // To be safe, only keep a reference to the
 6:         // *synchronized* list so that you are sure
 7:         // that all accesses are controlled.
 8:
 9:         // The collection *must* be synchronized
10:         // (a List in this case).
11:         List wordList =
12:                 Collections.synchronizedList(new ArrayList());
13:
14:         wordList.add("Iterators");
15:         wordList.add("require");
16:         wordList.add("special");
17:         wordList.add("handling");
18:
19:         // All of this must be in a synchronized block to
20:         // block other threads from modifying wordList while
21:         // the iteration is in progress.
22:         synchronized ( wordList ) {
23:             Iterator iter = wordList.iterator();
24:             while ( iter.hasNext() ) {
25:                 String s = (String) iter.next();
26:                 System.out.println("found string: " + s +
27:                     ", length=" + s.length());
28:             }
29:         }
30:     }
31: }

First, an ArrayList is wrapped in synchronization to ensure safe multithread access (lines 11–12). Next, four strings are added to the List (lines 14–17). A synchronized statement block is used to gain and hold the object-level lock for the List until the iteration is complete (lines 22–29). When the lock is safely held, an Iterator is retrieved by invoking the iterator() method (line 23) of Collection (List IS-A Collection). The hasNext() and next() methods of Iterator are used to traverse through the elements (lines 24–28). When the iteration is complete, the lock is released, and other threads (if there were any) are free to add and remove elements again.

Listing 7.28 shows the output produced when SafeCollectionIteration is run. Your output should match.

Example 7.28. Output from SafeCollectionIteration (Your Output Should Match)

found string: Iterators, length=9
found string: require, length=7
found string: special, length=7
found string: handling, length=8

Deadlocks

Using locks to control concurrent access to data is critical to avoid subtle race conditions within applications. However, trouble can arise when a thread needs to hold more than one lock at a time.

Imagine a situation where threadA currently has exclusive access to lock1. While threadA is holding its lock on lock1, threadB comes along and gets exclusive access to lock2. Next, while threadA still holds lock1, it tries to acquire lock2. Because threadB currently holds lock2, threadA blocks waiting for threadB to release it. So far, this is not a dangerous situation because when threadB releases lock2, threadA will unblock, acquire lock2, and complete its work.

A situation that will make trouble is if—while threadB is holding lock2—it needs to acquire lock1. Now threadA has exclusive access to lock1 and is trying to get exclusive access to lock2. At the same time, threadB has exclusive access to lock2 and is trying to get exclusive access to lock1. Both threadA and threadB will block forever waiting for the other to release its lock (see Figure 7.1). This situation is called a deadlock (it can also be called a deadly embrace).

A deadlock scenario.

Figure 7.1. A deadlock scenario.

The class Deadlock in Listing 7.29 demonstrates a situation where two threads end up in a deadlock with each other. Because of this, you will have to manually terminate the application after the main thread prints its last message.

Example 7.29. Deadlock.java—Deadlock Between Two Threads

 1: public class Deadlock extends Object {
 2:     private String objID;
 3:
 4:     public Deadlock(String id) {
 5:         objID = id;
 6:     }
 7:
 8:     public synchronized void checkOther(Deadlock other) {
 9:         print("entering checkOther()");
10:
11:         // simulate some lengthy process
12:         try { Thread.sleep(2000); }
13:         catch ( InterruptedException x ) { }
14:
15:         print("in checkOther() - about to " +
16:                 "invoke 'other.action()'");
17:         other.action();
18:
19:         print("leaving checkOther()");
20:     }
21:
22:     public synchronized void action() {
23:         print("entering action()");
24:
25:         // simulate some work here
26:         try { Thread.sleep(500); } 
27:         catch ( InterruptedException x ) { }
28:
29:         print("leaving action()");
30:     }
31:
32:     public void print(String msg) {
33:         threadPrint("objID=" + objID + " - " + msg);
34:     }
35:
36:     public static void threadPrint(String msg) {
37:         String threadName = Thread.currentThread().getName();
38:         System.out.println(threadName + ": " + msg);
39:     }
40: 
41:     public static void main(String[] args) {
42:         final Deadlock obj1 = new Deadlock("obj1");
43:         final Deadlock obj2 = new Deadlock("obj2");
44:
45:         Runnable runA = new Runnable() {
46:                 public void run() {
47:                     obj1.checkOther(obj2);
48:                 }
49:             };
50:
51:         Thread threadA = new Thread(runA, "threadA");
52:         threadA.start();
53:
54:         try { Thread.sleep(200); } 
55:         catch ( InterruptedException x ) { }
56:
57:         Runnable runB = new Runnable() {
58:                 public void run() {
59:                     obj2.checkOther(obj1);
60:                 }
61:             };
62:
63:         Thread threadB = new Thread(runB, "threadB");
64:         threadB.start();
65:
66:         try { Thread.sleep(5000); } 
67:         catch ( InterruptedException x ) { }
68:
69:         threadPrint("finished sleeping");
70:
71:         threadPrint("about to interrupt() threadA");
72:         threadA.interrupt();
73:
74:         try { Thread.sleep(1000); } 
75:         catch ( InterruptedException x ) { }
76:
77:         threadPrint("about to interrupt() threadB");
78:         threadB.interrupt();
79:
80:         try { Thread.sleep(1000); } 
81:         catch ( InterruptedException x ) { }
82: 
83:         threadPrint("did that break the deadlock?");
84:     }
85: }

In the main() method (lines 41–84), two instances of Deadlock are created and two threads are started—each one running one of the instances. The first instance has its objID set to obj1; the second is known as obj2 (lines 42–43). threadA is started and invokes the checkOther() method of obj1 passing in a reference to obj2 (line 47). After a quick 200 millisecond sleep, threadB is started and invokes the checkOther() method of obj2 passing in a reference to obj1 (line 59).

The checkOther() method (lines 8–20) is synchronized and requires a thread to get exclusive access to the instance's object-level lock before entering. After a thread gets inside, it prints a message (line 9) and then sleeps for two seconds to simulate a long-running task (line 12). After it wakes up, it prints a message and attempts to invoke the action() method of the other Deadlock instance (line 17). This will require that it get exclusive access to the object-level lock of the other instance because action() is synchronized (line 22).

Neither threadA nor threadB will ever get into the action() method of the other instance. While inside checkOther(), threadA is holding the object-level lock on obj1. threadB is simultaneously inside the checkOther() method of obj2 and is holding its object-level lock. When threadA tries to invoke the action() method on obj2, it will block waiting for threadB to release the lock. A fraction of a second later, threadB tries to invoke the action() method on obj1 and blocks waiting for threadA to release its lock. The two threads are deadlocked.

The main thread sleeps for five seconds while the deadlock is created (line 66). When it wakes up, it tries to break the deadlock by interrupting threadA (line 72). After one second, it tries to interrupt threadB (line 78). Unfortunately, this does not break the deadlock. When a thread is blocked waiting to acquire a lock, it does not respond to interrupts (refer to Chapter 6).

Listing 7.30 shows the output produced when Deadlock is run. Your output should match. Remember that you will have to manually terminate the application after about 15 seconds.

Example 7.30. Output Produced from Deadlock (Your Output Should Match)

1: threadA: objID=obj1 - entering checkOther()
2: threadB: objID=obj2 - entering checkOther()
3: threadA: objID=obj1 - in checkOther() - about to invoke 'other.action()'
4: threadB: objID=obj2 - in checkOther() - about to invoke 'other.action()'
5: main: finished sleeping
6: main: about to interrupt() threadA
7: main: about to interrupt() threadB
8: main: did that break the deadlock?

First threadA enters the checkObject() method of obj1 (line 1). Shortly thereafter, threadB enters the checkObject() method of obj2 (line 2). threadA wakes up first and tries to invoke the action() method of obj2, but blocks waiting for threadB to release the lock. Then threadB wakes up and tries to invoke the action() method of obj1, and also blocks waiting for the lock that is currently held by threadA. The attempts to interrupt the blocked threads have no effect (lines 6–7).

This is a bit of a contrived situation, but deadlocks are a very real problem and can arise in other ways too. Nothing limits a deadlock to only two threads. Any number of threads can get into a deadlock situation with each other.

Deadlock Avoidance

Deadlocks can be extremely difficult to track down. Generally, most of an application will continue to run, but a couple of threads will be stuck in a deadlock. To make matters worse, deadlocks can hide in code for quite a while, waiting for a rare condition to occur. An application can run fine 99 out of 100 times and only deadlock when the thread scheduler happens to run the threads in a slightly different order. Deadlock avoidance is a difficult task.

Most code is not vulnerable to deadlocks, but for the code that is, try following these guidelines to help avoid deadlocks:

  • Hold locks for only the minimal amount of time necessary. Consider using synchronized statement blocks instead of synchronizing the whole method.

  • Try to write code that does not need to hold more than one lock at a time. If this is unavoidable, try to make sure that threads hold the second lock only for a brief period of time.

  • Create and use one big lock instead of several small ones. Use this lock for mutual exclusion instead of the object-level locks of the individual objects.

  • Check out the InterruptibleSyncBlock class in Chapter 17. It uses another object to control concurrent access to a section of code. Additionally, instead of having a thread block on the synchronized statement, the thread is put into a wait-state that is interruptible. I'll tell you more about the wait-notify mechanism in Chapter 8.

Speeding Concurrent Access

Synchronization is critical to writing multithread-safe code. Synchronization does come at a cost. The simple task of acquiring and releasing a lock adds more work for the processor and slows execution. This extra processing cost is why the methods in the Collections API are not synchronized by default. When only one thread is working with a collection, synchronization is a waste of processor resources. However, many of the classes can be wrapped in synchronization when access by two or more threads makes it necessary.

To speed up execution, do not use synchronized unnecessarily. Be sure that it's really needed for proper functioning. If synchronization is necessary, see if using a synchronized statement block would work instead of a synchronized method. Although this won't decrease the cost of acquiring and releasing the lock, it will reduce contention for the lock among the other threads because the lock is held for a shorter period of time.

Tip

As you gain expertise in multithreaded programming, you'll begin to have a better feeling for where synchronization is and is not required. As you start out, you should err on the side of using too much synchronization (but watch out for deadlocks!). This way, the code might be slower than it could be, but it will be less likely to be missing synchronization where it is critically needed.

Summary

Having multiple threads running within an application can improve its performance and responsiveness to a user. Inevitably, these threads will have to interact with each other. Extra steps like synchronization have to be taken within the code to ensure that the threads do not corrupt any data and do not read any data that is in a temporarily inconsistent state.

In this chapter, I showed you:

  • How to use volatile to force unsynchronized threads to work with the shared copy of a variable instead of a private working copy.

  • How to use the synchronized method modifier on non-static methods to require a thread to get exclusive access to the object-level lock before entering the method.

  • How to use the synchronized statement block to require a thread to get exclusive access to the object-level lock of the specified object before executing the code within the block.

  • How to use the synchronized method modifier on static methods to require a thread to get exclusive access to the class-level lock before entering the method.

  • How to work safely with the Collections API in a multithreaded environment.

  • How to understand the causes of deadlocks, and how to try to avoid them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.19.220