Thread States

Chapter 7 - Concurrent Access to Objects and Variables

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

volatile Member Variable Modifier
The volatile keyword is used as a modifier on member variables to force individual threads to reread the variables 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 dont 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. Ill 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.
Listing 7.1  Volatile.javaDemonstration 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:     // doesnt need to be volatile-doesnt 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 92110), 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 99100). 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 1646). It continues to execute the while loop (lines 2043) 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 1014), value was initialized to 10 . The only place that value is changed is inside workMethod() (line 54). In case the new thread doesnt 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 3032). 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 3639). 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 4867). 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 , youll 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.
Listing 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 Microsystems 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.
Listing 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 dont 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 cant 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 applications execution.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net