- •Contents
- •Preface
- •Introduction to Computers, the Internet and the Web
- •1.3 Computer Organization
- •Languages
- •1.9 Java Class Libraries
- •1.12 The Internet and the World Wide Web
- •1.14 General Notes about Java and This Book
- •Sections
- •Introduction to Java Applications
- •2.4 Displaying Text in a Dialog Box
- •2.5 Another Java Application: Adding Integers
- •2.8 Decision Making: Equality and Relational Operators
- •Introduction to Java Applets
- •3.2 Sample Applets from the Java 2 Software Development Kit
- •3.3 A Simple Java Applet: Drawing a String
- •3.4 Two More Simple Applets: Drawing Strings and Lines
- •3.6 Viewing Applets in a Web Browser
- •3.7 Java Applet Internet and World Wide Web Resources
- •Repetition)
- •Class Attributes
- •5.8 Labeled break and continue Statements
- •5.9 Logical Operators
- •Methods
- •6.2 Program Modules in Java
- •6.7 Java API Packages
- •6.13 Example Using Recursion: The Fibonacci Series
- •6.16 Methods of Class JApplet
- •Class Operations
- •Arrays
- •7.6 Passing Arrays to Methods
- •7.8 Searching Arrays: Linear Search and Binary Search
- •Collaboration Among Objects
- •8.2 Implementing a Time Abstract Data Type with a Class
- •8.3 Class Scope
- •8.4 Controlling Access to Members
- •8.5 Creating Packages
- •8.7 Using Overloaded Constructors
- •8.9 Software Reusability
- •8.10 Final Instance Variables
- •Classes
- •8.16 Data Abstraction and Encapsulation
- •9.2 Superclasses and Subclasses
- •9.5 Constructors and Finalizers in Subclasses
- •Conversion
- •9.11 Type Fields and switch Statements
- •9.14 Abstract Superclasses and Concrete Classes
- •9.17 New Classes and Dynamic Binding
- •9.18 Case Study: Inheriting Interface and Implementation
- •9.19 Case Study: Creating and Using Interfaces
- •9.21 Notes on Inner Class Definitions
- •Strings and Characters
- •10.2 Fundamentals of Characters and Strings
- •10.21 Card Shuffling and Dealing Simulation
- •Handling
- •Graphics and Java2D
- •11.2 Graphics Contexts and Graphics Objects
- •11.5 Drawing Lines, Rectangles and Ovals
- •11.9 Java2D Shapes
- •12.12 Adapter Classes
- •Cases
- •13.3 Creating a Customized Subclass of JPanel
- •Applications
- •Controller
- •Exception Handling
- •14.6 Throwing an Exception
- •14.7 Catching an Exception
- •Multithreading
- •15.3 Thread States: Life Cycle of a Thread
- •15.4 Thread Priorities and Thread Scheduling
- •15.5 Thread Synchronization
- •15.9 Daemon Threads
- •Multithreading
- •Design Patterns
- •Files and Streams
- •16.2 Data Hierarchy
- •16.3 Files and Streams
- •Networking
- •17.2 Manipulating URIs
- •17.3 Reading a File on a Web Server
- •17.4 Establishing a Simple Server Using Stream Sockets
- •17.5 Establishing a Simple Client Using Stream Sockets
- •17.9 Security and the Network
- •18.2 Loading, Displaying and Scaling Images
- •18.3 Animating a Series of Images
- •18.5 Image Maps
- •18.6 Loading and Playing Audio Clips
- •18.7 Internet and World Wide Web Resources
- •Data Structures
- •19.4 Linked Lists
- •20.8 Bit Manipulation and the Bitwise Operators
- •Collections
- •21.8 Maps
- •21.9 Synchronization Wrappers
- •21.10 Unmodifiable Wrappers
- •22.2 Playing Media
- •22.3 Formatting and Saving Captured Media
- •22.5 Java Sound
- •22.8 Internet and World Wide Web Resources
- •Hexadecimal Numbers
848 |
Multithreading |
Chapter 15 |
15.5 Thread Synchronization
Java uses monitors (as discussed by C.A.R. Hoare in his 1974 paper cited in Exercise 15.24) to perform synchronization. Every object with synchronized methods has a monitor. The monitor allows one thread at a time to execute a synchronized method on the object. This is accomplished by locking the object when the program invokes the synchronized meth- od—also known as obtaining the lock. If there are several synchronized methods, only one synchronized method may be active on an object at once; all other threads attempting to invoke synchronized methods must wait. When a synchronized method finishes executing, the lock on the object is released and the monitor lets the highest priority-ready thread attempting to invoke a synchronized method proceed. [Note: Java also has synchronized blocks of code, which are discussed in the example of Section 15.10].
A thread executing in a synchronized method may determine that it cannot proceed, so the thread voluntarily calls wait. This removes the thread from contention for the processor and from contention for the monitor object. The thread now waits in the waiting state while other threads try to enter the monitor object. When a thread executing a synchronized method completes or satisfies the condition on which another thread may be waiting, the thread can notify a waiting thread to become ready again. At this point, the original thread can attempt to reacquire the lock on the monitor object and execute. The notify acts as a signal to the waiting thread that the condition for which the waiting thread has been waiting is now satisfied, so the waiting thread can reenter the monitor. If a thread calls notifyAll, then all threads waiting for the monitor become eligible to reenter the monitor (that is, they are all placed in a ready state). Remember that only one of those threads can obtain the lock on the object at a time—other threads that attempt to acquire the same lock will be blocked by the operating system until the lock becomes available again. Methods wait, notify and notifyAll are inherited by all classes from class Object. So any object may have a monitor.
Common Programming Error 15.1
Threads in the waiting state for a monitor object must be awakened explicitly with a notify (or interrupt) or the thread will wait forever. This may cause deadlock. [Note: There are versions of method wait that receive arguments indicating the maximum wait time. If the thread is not notified in the specified amount of time, the thread becomes ready to execute.]
Testing and Debugging Tip 15.3
Be sure that every call to wait has a corresponding call to notify that eventually will end the waiting or call notifyAll as a safeguard.
Performance Tip 15.4
Synchronization to achieve correct multithreaded behavior can make programs run more slowly due to the monitor overhead and frequently moving threads between the running, waiting and ready states. However, there is not much to say for highly efficient, incorrect multithreaded programs!
Testing and Debugging Tip 15.4
The locking that occurs with the execution of synchronized methods could lead to deadlock if the locks are never released. When exceptions occur, Java’s exception mechanism coordinates with Java’s synchronization mechanism to release appropriate synchronization locks to avoid these kinds of deadlocks.
Chapter 15 |
Multithreading |
849 |
Monitor objects maintain a list of all threads waiting to enter the monitor object to execute synchronized methods. A thread is inserted in the list and waits for the object if that thread calls a synchronized method of the object while another thread is already executing in a synchronized method of that object. A thread also is inserted in the list if the thread calls wait while operating inside the object. However, it is important to distinguish between waiting threads that blocked because the monitor was busy and threads that explicitly called wait inside the monitor. Upon completion of a synchronized method, outside threads that blocked because the monitor was busy can proceed to enter the object. Threads that explicitly invoked wait can proceed only when notified via a call by another thread to notify or notifyAll. When it is acceptable for a waiting thread to proceed, the scheduler selects the thread with the highest priority.
Common Programming Error 15.2
It is an error if a thread issues a wait, a notify or a notifyAll on an object without
having acquired a lock for the object. This causes an IllegalMonitorStateException.
15.6 Producer/Consumer Relationship without Thread Synchronization
In a producer/consumer relationship, a producer thread calling a produce method may see that the consumer thread has not read the last message from a shared region of memory called a buffer, so the producer thread will call wait. When a consumer thread reads the message, it will call notify to allow a waiting producer to proceed. When a consumer thread enters the monitor and finds the buffer empty, it calls wait. A producer finding the buffer empty writes to the buffer, then calls notify so a waiting consumer can proceed.
Shared data can get corrupted if we do not synchronize access among multiple threads. Consider a producer/consumer relationship in which a producer thread deposits a sequence of numbers (we use 1, 2, 3, …) into a slot of shared memory. The consumer thread reads this data from the shared memory and prints it. We print what the producer produces as it produces it and what the consumer consumes as it consumes it. The program of Fig. 15.4– Fig. 15.7 demonstrates a producer and a consumer accessing a single shared cell of memory (int variable sharedInt in Fig. 15.6) without any synchronization. The threads are not synchronized, so data can be lost if the producer places new data into the slot before the consumer consumes the previous data. Also, data can be “doubled” if the consumer consumes data again before the producer produces the next item. To show these possibilities, the consumer thread in this example keeps a total of all the values it reads. The producer thread produces values from 1 to 10. If the consumer reads each value produced only once, the total would be 55. However, if you execute this program several times, you will see that the total is rarely, if ever, 55.
The program consists of four classes—ProduceInteger (Fig. 15.4), ConsumeInteger (Fig. 15.5), HoldIntegerUnsynchronized (Fig. 15.6) and
SharedCell (Fig. 15.7).
Class ProduceInteger (Fig. 15.4)—a subclass of Thread—consists of instance variable sharedObject (line 4), a constructor (lines 7–11) and a run method (lines 15– 37). The constructor initializes instance variable sharedObject (line 10) to refer to the
850 |
Multithreading |
Chapter 15 |
HoldIntegerUnsynchronized object shared, which was passed as an argument. Class ProduceInteger’s run method (lines 15–37) consists of a for structure that loops 10 times. Each iteration of the loop first invokes method sleep to put the ProduceInteger object into the sleeping state for a random time interval between 0 and 3 seconds. When the thread awakens, line 31 calls class HoldIntegerUnsynchronized’s setSharedInt method and passes the value of control variable count to set the shared object’s instance variable sharedInt. When the loop completes, lines 34–36 display a line of text in the command window indicating that the thread finished producing data and that the thread is terminating, then the thread terminates (i.e., the thread dies).
1// Fig. 15.4: ProduceInteger.java
2 // Definition of threaded class ProduceInteger
3public class ProduceInteger extends Thread {
4 private HoldIntegerUnsynchronized sharedObject;
5
6// initialize ProduceInteger thread object
7public ProduceInteger( HoldIntegerUnsynchronized shared )
8{
9 super( "ProduceInteger" );
10sharedObject = shared;
11}
12
13// ProduceInteger thread loops 10 times and calls
14// sharedObject's setSharedInt method each time
15public void run()
16{
17for ( int count = 1; count <= 10; count++ ) {
19 |
// sleep for a random interval |
20 |
try { |
21 |
Thread.sleep( ( int ) ( Math.random() * 3000 ) ); |
22 |
} |
23 |
|
24 |
// process InterruptedException during sleep |
25 |
catch( InterruptedException exception ) { |
26 |
System.err.println( exception.toString() ); |
27 |
} |
28 |
|
29 |
// call sharedObject method from this |
30 |
// thread of execution |
31sharedObject.setSharedInt( count );
32}
33 |
|
34 |
System.err.println( |
35 |
getName() + " finished producing values" + |
36"\nTerminating " + getName() );
37}
38
39 } // end class ProduceInteger
Fig. 15.4 Class ProduceInteger represents the producer in a producer/ consumer relationship.
Chapter 15 |
Multithreading |
851 |
Class ConsumeInteger (Fig. 15.5)—a subclass of Thread—consists of instance variable sharedObject (line 4), a constructor (lines 7–11) and a run method (lines 15– 39). The constructor initializes instance variable sharedObject (line 10) to refer to the HoldIntegerUnsynchronized object shared that was passed as an argument. Class ConsumeInteger’s run method (lines 15–39) consists of a do/while structure that loops until the value 10 is read from the HoldIntegerUnsynchronized object to which sharedObject refers. Each iteration of the loop invokes method sleep to put the ConsumeInteger object into the sleeping state for a random time interval between 0 and 3 seconds. Next, line 31 invokes class HoldIntegerUnsynchronized’s getSharedInt method to get the value of the shared object’s instance variable sharedInt. Then, line 32 adds the value returned by getSharedInt to the variable sum. When the loop completes, the ConsumeInteger thread displays a line in the command window indicating that it has finished consuming data and terminates (i.e., the thread dies).
1// Fig. 15.5: ConsumeInteger.java
2 // Definition of threaded class ConsumeInteger
3public class ConsumeInteger extends Thread {
4 private HoldIntegerUnsynchronized sharedObject;
5
6// initialize ConsumerInteger thread object
7public ConsumeInteger( HoldIntegerUnsynchronized shared )
8{
9 super( "ConsumeInteger" );
10sharedObject = shared;
11}
12
13// ConsumeInteger thread loops until it receives 10
14// from sharedObject's getSharedInt method
15public void run()
16{
17int value, sum = 0;
18 |
|
19 |
do { |
20 |
|
21 |
// sleep for a random interval |
22 |
try { |
23 |
Thread.sleep( (int) ( Math.random() * 3000 ) ); |
24 |
} |
25 |
|
26 |
// process InterruptedException during sleep |
27 |
catch( InterruptedException exception ) { |
28 |
System.err.println( exception.toString() ); |
29 |
} |
30 |
|
31 |
value = sharedObject.getSharedInt(); |
32 |
sum += value; |
33 |
|
34 |
} while ( value != 10 ); |
35 |
|
Fig. 15.5 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 1 of 2).
852 |
Multithreading |
Chapter 15 |
|
|
|
36 |
System.err.println( |
|
37 |
getName() + " retrieved values totaling: " + sum + |
38"\nTerminating " + getName() );
39}
40
41 } // end class ConsumeInteger
Fig. 15.5 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 2 of 2).
Class HoldIntegerUnsynchronized consists of instance variable sharedInt
(line 4), method setSharedInt (lines 7–13) and method getSharedInt (lines 16– 22). Methods setSharedInt and getSharedInt do not synchronize access to instance variable sharedInt. Note that each method uses static Thread method currentThread to obtain a reference to the currently executing thread, then use Thread method getName to obtain the thread’s name.
Class SharedCell’s main method (lines 6–20) instantiates the shared HoldIntegerUnsynchronized object sharedObject and uses it as the argument to the constructors for the ProduceInteger object producer and the ConsumeInteger object consumer. The sharedObject contains the data that will be shared between the two threads. Next, method main invokes the Thread class start method on the producer and consumer threads to place them in the ready state. This launches these threads.
1// Fig. 15.6: HoldIntegerUnsynchronized.java
2 // Definition of class HoldIntegerUnsynchronized.
3 public class HoldIntegerUnsynchronized {
4 private int sharedInt = -1;
5
6 // unsynchronized method to place value in sharedInt
7public void setSharedInt( int value )
8{
9System.err.println( Thread.currentThread().getName() +
10 |
" setting sharedInt to " + value ); |
11 |
|
12sharedInt = value;
13}
14
15// unsynchronized method return sharedInt's value
16public int getSharedInt()
17{
18System.err.println( Thread.currentThread().getName() +
19 |
" retrieving sharedInt value " + sharedInt ); |
20 |
|
21return sharedInt;
22}
23
24 } // end class HoldIntegerUnsynchronized
Fig. 15.6 Class HoldIntegerUnsynchronized maintains the data shared between the producer and consumer threads.
Chapter 15 |
Multithreading |
853 |
1// Fig. 15.7: SharedCell.java
2 // Show multiple threads modifying shared object.
3 public class SharedCell {
4
5// execute application
6public static void main( String args[] )
7{
8HoldIntegerUnsynchronized sharedObject =
9 new HoldIntegerUnsynchronized();
10
11// create threads
12ProduceInteger producer =
13new ProduceInteger( sharedObject );
14ConsumeInteger consumer =
15 |
new ConsumeInteger( sharedObject ); |
16 |
|
17// start threads
18producer.start();
19consumer.start();
20}
21
22 } // end class SharedCell
ConsumeInteger retrieving sharedInt value -1
ConsumeInteger retrieving sharedInt value -1
ProduceInteger setting sharedInt to 1
ProduceInteger setting sharedInt to 2
ConsumeInteger retrieving sharedInt value 2
ProduceInteger setting sharedInt to 3
ProduceInteger setting sharedInt to 4
ProduceInteger setting sharedInt to 5
ConsumeInteger retrieving sharedInt value 5
ProduceInteger setting sharedInt to 6
ProduceInteger setting sharedInt to 7
ProduceInteger setting sharedInt to 8
ConsumeInteger retrieving sharedInt value 8
ConsumeInteger retrieving sharedInt value 8
ProduceInteger setting sharedInt to 9
ConsumeInteger retrieving sharedInt value 9
ConsumeInteger retrieving sharedInt value 9
ProduceInteger setting sharedInt to 10
ProduceInteger finished producing values
Terminating ProduceInteger
ConsumeInteger retrieving sharedInt value 10
ConsumeInteger retrieved values totaling: 49
Terminating ConsumeInteger
Fig. 15.7 Threads modifying a shared object without synchronization.
Ideally, we would like every value produced by the ProduceInteger object to be consumed exactly once by the ConsumeInteger object. However, when we study the output of Fig. 15.7, we see that the values 1, 3, 4, 6 and 7 are lost (i.e., never seen by the consumer) and that the values 8 and 9 are incorrectly retrieved more than once by the con-
854 |
Multithreading |
Chapter 15 |
sumer. Also, notice that the consumer twice retrieved value –1 (the default value of sharedInt set at line 5 of Fig. 15.6) before the producer ever assigned 1 to the sharedInt variable. This example clearly demonstrates that access to shared data by concurrent threads must be controlled carefully or a program may produce incorrect results.
To solve the problems of lost data and doubled data in the previous example, we will synchronize access of the concurrent producer and consumer threads to the shared data. Each method used by a producer or consumer to access the shared data is declared with the synchronized keyword. When a method declared synchronized is running in an object, the object is locked so no other synchronized method can run in that object at the same time.
15.7 Producer/Consumer Relationship with Thread Synchronization
The application in Fig. 15.8 demonstrates a producer and a consumer accessing a shared cell of memory with synchronization so that the consumer only consumes after the produc-
er produces a |
value. Classes ProduceInteger (Fig. 15.8), ConsumeInteger |
(Fig. 15.9) and |
SharedCell (Fig. 15.11) are identical to Fig. 15.4, Fig. 15.5 and |
Fig. 15.7 except that they use the new class HoldIntegerSynchronized in this example.
Class HoldIntegerSynchronized (Fig. 15.10) contains two instance vari- ables—sharedInt (line 6) and writeable (line 7). Also, method setSharedInt (lines 12–39) and method getSharedInt (lines 44–70) are now synchronized methods. Objects of class HoldIntegerSynchronized have monitors, because
HoldIntegerSynchronized contains synchronized methods. Instance variable writeable is known as the monitor’s condition variable—is a boolean used by methods setSharedInt and getSharedInt of class HoldIntegerSynchronized. If writeable is true, setSharedInt can place a value into variable sharedInt, because the variable currently does not contain information. However, this means getSharedInt currently cannot read the value of sharedInt. If writeable is false, getSharedInt can read a value from variable sharedInt because the variable currently does contain information. However, this means setSharedInt currently cannot place a value into sharedInt.
1// Fig. 15.8: ProduceInteger.java
2 // Definition of threaded class ProduceInteger
3public class ProduceInteger extends Thread {
4 private HoldIntegerSynchronized sharedObject;
5
6// initialize ProduceInteger thread object
7public ProduceInteger( HoldIntegerSynchronized shared )
8{
9 super( "ProduceInteger" );
10sharedObject = shared;
11}
12
Fig. 15.8 Class ProduceInteger represents the producer in a producer/ consumer relationship (part 1 of 2).
Chapter 15 |
Multithreading |
855 |
13// ProduceInteger thread loops 10 times and calls
14// sharedObject's setSharedInt method each time
15public void run()
16{
17for ( int count = 1; count <= 10; count++ ) {
19 |
// sleep for a random interval |
20 |
try { |
21 |
Thread.sleep( ( int ) ( Math.random() * 3000 ) ); |
22 |
} |
23 |
|
24 |
// process InterruptedException during sleep |
25 |
catch( InterruptedException exception ) { |
26 |
System.err.println( exception.toString() ); |
27 |
} |
28 |
|
29 |
// call sharedObject method from this |
30 |
// thread of execution |
31sharedObject.setSharedInt( count );
32}
33 |
|
34 |
System.err.println( |
35 |
getName() + " finished producing values" + |
36"\nTerminating " + getName() );
37}
38
39 } // end class ProduceInteger
Fig. 15.8 Class ProduceInteger represents the producer in a producer/ consumer relationship (part 2 of 2).
1// Fig. 15.9: ConsumeInteger.java
2 // Definition of threaded class ConsumeInteger
3public class ConsumeInteger extends Thread {
4 private HoldIntegerSynchronized sharedObject;
5
6// initialize ConsumerInteger thread object
7public ConsumeInteger( HoldIntegerSynchronized shared )
8{
9 super( "ConsumeInteger" );
10sharedObject = shared;
11}
12
13// ConsumeInteger thread loops until it receives 10
14// from sharedObject's getSharedInt method
15public void run()
16{
17int value, sum = 0;
18
Fig. 15.9 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 1 of 2).
856 |
Multithreading |
Chapter 15 |
|
|
|
19 |
do { |
|
20 |
|
|
21 |
// sleep for a random interval |
|
22 |
try { |
|
23 |
Thread.sleep( (int) ( Math.random() * 3000 ) ); |
|
24 |
} |
|
25 |
|
|
26 |
// process InterruptedException during sleep |
|
27 |
catch( InterruptedException exception ) { |
|
28 |
System.err.println( exception.toString() ); |
|
29 |
} |
|
30 |
|
|
31 |
value = sharedObject.getSharedInt(); |
|
32 |
sum += value; |
|
33 |
|
|
34 |
} while ( value != 10 ); |
|
35 |
|
|
36 |
System.err.println( |
|
37 |
getName() + " retrieved values totaling: " + sum + |
38"\nTerminating " + getName() );
39}
40
41 } // end class ConsumeInteger
Fig. 15.9 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 2 of 2).
When the ProduceInteger thread object invokes synchronized method setSharedInt (line 31 of Fig. 15.8), the thread acquires a lock on the HoldIntegerSynchronized monitor object. The while structure in HoldIntegerSynchronized at lines 14–25 tests the writeable variable with the condition !writeable. If this condition is true, the thread invokes method wait. This places the ProduceInteger thread object that called method setSharedInt into the waiting state for the HoldIntegerSynchronized object and releases the lock on it. Now another thread can invoke a synchronized method on the HoldIntegerSynchronized object.
The ProduceInteger object remains in the waiting state until it is notified that it may proceed—at which point it enters the ready state and waits for the system to assign a processor to it. When the ProduceInteger object reenters the running state, it reacquires the lock on the HoldIntegerSynchronized object implicitly and the setSharedInt method continues executing in the while structure with the next statement after wait. There are no more statements, so the program reevaluates the while condition. If the condition is false, the program outputs a line to the command window indicating that the producer is setting sharedInt to a new value, assigns value to sharedInt, sets writeable to false to indicate that the shared memory is now full (i.e., a consumer can read the value and a producer cannot put another value there yet) and invokes method notify. If there are any waiting threads, one of those waiting threads enters the ready state, indicating that the thread can now attempt its task again (as soon as it is assigned a processor). The notify method returns immediately and method setSharedInt returns to its caller.
Chapter 15 |
Multithreading |
857 |
1// Fig. 15.10: HoldIntegerSynchronized.java
2 // Definition of class HoldIntegerSynchronized that
3 // uses thread synchronization to ensure that both
4 // threads access sharedInt at the proper times.
5 public class HoldIntegerSynchronized {
6private int sharedInt = -1;
7 |
private boolean writeable = true; // condition variable |
8 |
|
9// synchronized method allows only one thread at a time to
10// invoke this method to set the value for a particular
11// HoldIntegerSynchronized object
12public synchronized void setSharedInt( int value )
13{
14while ( !writeable ) { // not the producer's turn
16 |
// thread that called this method must wait |
17 |
try { |
18 |
wait(); |
19 |
} |
20 |
|
21 |
// process Interrupted exception while thread waiting |
22 |
catch ( InterruptedException exception ) { |
23 |
exception.printStackTrace(); |
24}
25}
26
27 System.err.println( Thread.currentThread().getName() +
28 " setting sharedInt to " + value );
29
30// set new sharedInt value
31sharedInt = value;
32
33// indicate that producer cannot store another value until
34// a consumer retrieve current sharedInt value
35writeable = false;
36
37// tell a waiting thread to become ready
38notify();
39}
40
41// synchronized method allows only one thread at a time to
42// invoke this method to get the value for a particular
43// HoldIntegerSynchronized object
44public synchronized int getSharedInt()
45{
46while ( writeable ) { // not the consumer's turn
47 |
|
48 |
// thread that called this method must wait |
49 |
try { |
50 |
wait(); |
51 |
} |
52 |
|
Fig. 15.10 Class HoldIntegerSynchronized monitors access to a shared integer (part 1 of 2).
858 |
Multithreading |
Chapter 15 |
|
|
|
53 |
// process Interrupted exception while thread waiting |
|
54 |
catch ( InterruptedException exception ) { |
|
55 |
exception.printStackTrace(); |
|
56}
57}
58
59// indicate that producer cant store another value
60// because a consumer just retrieved sharedInt value
61writeable = true;
62
63// tell a waiting thread to become ready
64notify();
65
66 System.err.println( Thread.currentThread().getName() +
67 " retrieving sharedInt value " + sharedInt );
68
69return sharedInt;
70}
71
72 } // end class HoldIntegerSynchronized
Fig. 15.10 Class HoldIntegerSynchronized monitors access to a shared integer (part 2 of 2).
Methods getSharedInt and setSharedInt are implemented similarly. When the ConsumeInteger object invokes method getSharedInt, the calling thread acquires a lock on the HoldIntegerSynchronized object. The while structure at lines 46–57 tests the writeable variable. If writeable is true (i.e., there is nothing to consume), the thread invokes method wait. This places the ConsumeInteger thread object that called method getSharedInt into the waiting state for the HoldIntegerSynchronized object and releases the lock on it so other synchronized methods can be invoked on the object. The ConsumeInteger object remains in the waiting state until it is notified that it may proceed—at which point it enters the ready state and waits to be assigned a processor. When the ConsumeInteger object reenters the running state, the thread reacquires the lock on the HoldIntegerSynchronized object and the getSharedInt method continues executing in the while structure with the next statement after wait. There are no more statements, so the program tests the while condition again. If the condition is false, the program sets writeable to true to indicate that the shared memory is now empty and invokes method notify. If there are any waiting threads, one of those waiting threads enters the ready state, indicating that the thread can now attempt its task again (as soon as it is assigned a processor). The notify method returns immediately. Then, getSharedInt outputs a line to the command window indicating that the consumer is retrieving sharedInt, then returns the value of sharedInt to getSharedInt’s caller.
Study the output in Fig. 15.11. Observe that every integer produced is consumed once—no values are lost and no values are doubled. Also, the consumer cannot read a value until the producer produces a value.
Chapter 15 |
Multithreading |
859 |
1// Fig. 15.11: SharedCell.java
2 // Show multiple threads modifying shared object.
3 public class SharedCell {
4
5// execute application
6public static void main( String args[] )
7{
8HoldIntegerSynchronized sharedObject =
9 new HoldIntegerSynchronized();
10
11// create threads
12ProduceInteger producer =
13new ProduceInteger( sharedObject );
14ConsumeInteger consumer =
15 |
new ConsumeInteger( sharedObject ); |
16 |
|
17// start threads
18producer.start();
19consumer.start();
20}
21
22 } // end class SharedCell
ProduceInteger setting sharedInt to 1
ConsumeInteger retrieving sharedInt value 1
ProduceInteger setting sharedInt to 2
ConsumeInteger retrieving sharedInt value 2
ProduceInteger setting sharedInt to 3
ConsumeInteger retrieving sharedInt value 3
ProduceInteger setting sharedInt to 4
ConsumeInteger retrieving sharedInt value 4
ProduceInteger setting sharedInt to 5
ConsumeInteger retrieving sharedInt value 5
ProduceInteger setting sharedInt to 6
ConsumeInteger retrieving sharedInt value 6
ProduceInteger setting sharedInt to 7
ConsumeInteger retrieving sharedInt value 7
ProduceInteger setting sharedInt to 8
ConsumeInteger retrieving sharedInt value 8
ProduceInteger setting sharedInt to 9
ConsumeInteger retrieving sharedInt value 9
ProduceInteger setting sharedInt to 10
ProduceInteger finished producing values
Terminating ProduceInteger
ConsumeInteger retrieving sharedInt value 10
ConsumeInteger retrieved values totaling: 55
Terminating ConsumeInteger
Fig. 15.11 Threads modifying a shared object with synchronization.
860 |
Multithreading |
Chapter 15 |
15.8 Producer/Consumer Relationship: The Circular Buffer
The program of Fig. 15.8–Fig. 15.11 does access the shared data correctly, but it may not perform optimally. Because the threads are running asynchronously, we cannot predict their relative speeds. If the producer wants to produce faster than the consumer can consume, it cannot do so. To enable the producer to continue producing, we can use a circular buffer that has enough extra cells to handle the “extra” production. The program of Fig. 15.13–Fig. 15.16 demonstrates a producer and a consumer accessing a circular buffer (in this case, a shared array of five cells) with synchronization so that the consumer only consumes a value when there are one or more values in the array, and the producer only produces a value when there are one or more available cells in the array. This program is implemented as a windowed application that sends its output to a JTextArea. Class
SharedCell’s constructor creates the HoldIntegerSynchronized, ProduceInteger and ConsumeInteger objects. The HoldIntegerSynchronized object sharedObject’s constructor receives a reference to a JTextArea object in which the program’s output will appear.
This is the first program in which we use separate threads to modify the content displayed in Swing GUI components. The nature of multithreaded programming prevents the programmer from knowing exactly when a thread will execute. Swing components are not thread-safe—if multiple threads access a Swing GUI component, the results may not be correct. All interactions with Swing GUI components should be performed from one thread at a time. Normally, this thread is the event-dispatch thread (also known as the event-handling thread). Class SwingUtilities (package javax.swing) provides static method invokeLater to help with this process. Method invokeLater receives a Runnable argument. We will see in the next section that Runnable objects have a run method. In fact, class Thread implements interface Runnable, so all Threads have a run method. The threads in this example pass objects of class
UpdateThread (Fig. 15.13) to method invokeLater. Each UpdateThread object receives a reference to the JTextArea in which to append the output and the message to display. Method run of class UpdateThread appends the message to the JTextArea. When the program calls invokeLater, the GUI component update will be queued for execution in the event-dispatch thread. The run method will then be invoked as part of the event-dispatch thread, ensuring that the GUI component updates in a thread-safe manner.
Common Programming Error 15.3
Interactions with Swing GUI components that change or obtain property values of the com- ponents may have incorrect results if the interactions are performed from multiple threads.
Software Engineering Observation 15.2
Use SwingUtilities static method invokeLater to ensure that all GUI interac- tions are performed from the event-dispatch thread.
Class ProduceInteger (Fig. 15.13) has been modified slightly from the version presented in Fig. 15.8. The new version places its output in a JTextArea. ProduceInteger receives a reference to the JTextArea when the program calls the ProduceInteger constructor. Note the use of SwingUtilities method invokeLater in lines 42–44 to ensure that the GUI updates properly.
Chapter 15 |
Multithreading |
861 |
1// Fig. 15.12: UpdateThread.java
2 // Class for updating JTextArea with output.
3
4 // Java extension packages
5 import javax.swing.*;
6
7 public class UpdateThread extends Thread {
8private JTextArea outputArea;
9 private String messageToOutput;
10
11// initialize outputArea and message
12public UpdateThread( JTextArea output, String message )
13{
14outputArea = output;
15messageToOutput = message;
16}
17
18// method called to update outputArea
19public void run()
20{
21outputArea.append( messageToOutput );
22}
23
24 } // end class UpdateThread
Fig. 15.12 UpdateThread used by SwingUtilities method invokeLater to ensure GUI updates properly.
1// Fig. 15.13: ProduceInteger.java
2 // Definition of threaded class ProduceInteger
3
4 // Java extension packages
5 import javax.swing.*;
6
7public class ProduceInteger extends Thread {
8 private HoldIntegerSynchronized sharedObject;
9 private JTextArea outputArea;
10
11// initialize ProduceInteger
12public ProduceInteger( HoldIntegerSynchronized shared,
13JTextArea output )
14{
15super( "ProduceInteger" );
16
17sharedObject = shared;
18outputArea = output;
19}
20
21// ProduceInteger thread loops 10 times and calls
22// sharedObject's setSharedInt method each time
Fig. 15.13 Class ProduceInteger represents the producer in a producer/ consumer relationship (part 1 of 2).
862 |
Multithreading |
Chapter 15 |
23public void run()
24{
25for ( int count = 1; count <= 10; count++ ) {
27 |
// sleep for a random interval |
28 |
// Note: Interval shortened purposely to fill buffer |
29 |
try { |
30 |
Thread.sleep( (int) ( Math.random() * 500 ) ); |
31 |
} |
32 |
|
33 |
// process InterruptedException during sleep |
34 |
catch( InterruptedException exception ) { |
35 |
System.err.println( exception.toString() ); |
36 |
} |
37 |
|
38sharedObject.setSharedInt( count );
39}
40
41// update Swing GUI component
42SwingUtilities.invokeLater( new UpdateThread( outputArea,
43 |
"\n" + getName() + " finished producing values" + |
44"\nTerminating " + getName() + "\n" ) );
45}
46
47 } // end class ProduceInteger
Fig. 15.13 Class ProduceInteger represents the producer in a producer/ consumer relationship (part 2 of 2).
Class ConsumeInteger (Fig. 15.14) has been modified slightly from the version presented in Fig. 15.9. The new version places its output in a JTextArea. ConsumeInteger receives a reference to the JTextArea when the program calls the ConsumeInteger constructor. Note the use of SwingUtilities method invokeLater in lines 45–47 to ensure that the GUI updates properly.
1// Fig. 15.14: ConsumeInteger.java
2 // Definition of threaded class ConsumeInteger
3
4 // Java extension packages
5 import javax.swing.*;
6
7public class ConsumeInteger extends Thread {
8 private HoldIntegerSynchronized sharedObject;
9 private JTextArea outputArea;
10
11// initialize ConsumeInteger
12public ConsumeInteger( HoldIntegerSynchronized shared,
13JTextArea output )
14{
15super( "ConsumeInteger" );
Fig. 15.14 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 1 of 2).
Chapter 15 |
Multithreading |
863 |
16
17sharedObject = shared;
18outputArea = output;
19}
20
21// ConsumeInteger thread loops until it receives 10
22// from sharedObject's getSharedInt method
23public void run()
24{
25int value, sum = 0;
26 |
|
27 |
do { |
28 |
|
29 |
// sleep for a random interval |
30 |
try { |
31 |
Thread.sleep( (int) ( Math.random() * 3000 ) ); |
32 |
} |
33 |
|
34 |
// process InterruptedException during sleep |
35 |
catch( InterruptedException exception ) { |
36 |
System.err.println( exception.toString() ); |
37 |
} |
38 |
|
39 |
value = sharedObject.getSharedInt(); |
40 |
sum += value; |
41 |
|
42 |
} while ( value != 10 ); |
43 |
|
44// update Swing GUI component
45SwingUtilities.invokeLater( new UpdateThread( outputArea,
46 |
"\n" + getName() + " retrieved values totaling: " + |
47sum + "\nTerminating " + getName() + "\n" ) );
48}
49
50 } // end class ConsumeInteger
Fig. 15.14 Class ConsumeInteger represents the consumer in a producer/ consumer relationship (part 2 of 2).
Once again, the primary changes in this example are in the definition of class HoldIntegerSynchronized (Fig. 15.15). The class now contains six instance variables— sharedInt is a five-element integer array that is used as the circular buffer, writeable indicates if a producer can write into the circular buffer, readable indicates if a consumer can read from the circular buffer, readLocation indicates the current position from which the consumer can read the next value, writeLocation indicates the next location in which the producer can place a value and outputArea is the JTextArea used by the threads in this program to display output.
1 // Fig. 15.15: HoldIntegerSynchronized.java
2// Definition of class HoldIntegerSynchronized that
Fig. 15.15 Class HoldIntegerSynchronized monitors access to a shared array of integers (part 1 of 5).
864 |
Multithreading |
Chapter 15 |
3 // uses thread synchronization to ensure that both
4 // threads access sharedInt at the proper times.
5
6// Java core packages
7 import java.text.DecimalFormat;
8
9 // Java extension packages
10 import javax.swing.*;
11
12 public class HoldIntegerSynchronized {
13
14// array of shared locations
15private int sharedInt[] = { -1, -1, -1, -1, -1 };
17// variables to maintain buffer information
18private boolean writeable = true;
19private boolean readable = false;
20private int readLocation = 0, writeLocation = 0;
22// GUI component to display output
23private JTextArea outputArea;
24
25// initialize HoldIntegerSynchronized
26public HoldIntegerSynchronized( JTextArea output )
27{
28outputArea = output;
29}
30
31// synchronized method allows only one thread at a time to
32// invoke this method to set a value in a particular
33// HoldIntegerSynchronized object
34public synchronized void setSharedInt( int value )
35{
36while ( !writeable ) {
37 |
|
38 |
// thread that called this method must wait |
39 |
try { |
40 |
|
41 |
// update Swing GUI component |
42 |
SwingUtilities.invokeLater( new UpdateThread( |
43 |
outputArea, " WAITING TO PRODUCE " + value ) ); |
44 |
|
45 |
wait(); |
46 |
} |
47 |
|
48 |
// process InterrupteException while thread waiting |
49 |
catch ( InterruptedException exception ) { |
50 |
System.err.println( exception.toString() ); |
51}
52}
53
Fig. 15.15 Class HoldIntegerSynchronized monitors access to a shared array of integers (part 2 of 5).
Chapter 15 |
Multithreading |
865 |
54// place value in writeLocation
55sharedInt[ writeLocation ] = value;
57// indicate that consumer can read a value
58readable = true;
59
60// update Swing GUI component
61SwingUtilities.invokeLater( new UpdateThread( outputArea,
62 |
"\nProduced " |
+ value + " into cell " + |
63 |
writeLocation |
) ); |
64 |
|
|
65// update writeLocation for future write operation
66writeLocation = ( writeLocation + 1 ) % 5;
67
68// update Swing GUI component
69SwingUtilities.invokeLater( new UpdateThread( outputArea,
70 |
"\twrite " + writeLocation + "\tread " + |
71 |
readLocation ) ); |
72 |
|
73 |
displayBuffer( outputArea, sharedInt ); |
74 |
|
75// test if buffer is full
76if ( writeLocation == readLocation ) {
77 |
writeable = false; |
78 |
|
79 |
// update Swing GUI component |
80 |
SwingUtilities.invokeLater( new UpdateThread( outputArea, |
81 |
"\nBUFFER FULL" ) ); |
82 |
} |
83 |
|
84// tell a waiting thread to become ready
85notify();
86
87 } // end method setSharedInt
88
89// synchronized method allows only one thread at a time to
90// invoke this method to get a value from a particular
91// HoldIntegerSynchronized object
92public synchronized int getSharedInt()
93{
94int value;
95 |
|
96 |
while ( !readable ) { |
97 |
|
98 |
// thread that called this method must wait |
99 |
try { |
100 |
|
101 |
// update Swing GUI component |
102 |
SwingUtilities.invokeLater( new UpdateThread( |
103 |
outputArea, " WAITING TO CONSUME" ) ); |
104 |
|
Fig. 15.15 Class HoldIntegerSynchronized monitors access to a shared array of integers (part 3 of 5).
866 |
Multithreading |
Chapter 15 |
|
|
|
105 |
wait(); |
|
106 |
} |
|
107 |
|
|
108 |
// process InterrupteException while thread waiting |
|
109 |
catch ( InterruptedException exception ) { |
|
110 |
System.err.println( exception.toString() ); |
111}
112}
114// indicate that producer can write a value
115writeable = true;
116
117// obtain value at current readLocation
118value = sharedInt[ readLocation ];
119
120// update Swing GUI component
121SwingUtilities.invokeLater( new UpdateThread( outputArea,
122"\nConsumed " + value + " from cell " +
123readLocation ) );
125// update read location for future read operation
126readLocation = ( readLocation + 1 ) % 5;
127
128// update Swing GUI component
129SwingUtilities.invokeLater( new UpdateThread( outputArea,
130"\twrite " + writeLocation + "\tread " +
131readLocation ) );
133 displayBuffer( outputArea, sharedInt );
134
135// test if buffer is empty
136if ( readLocation == writeLocation ) {
137readable = false;
139 // update Swing GUI component
140 SwingUtilities.invokeLater( new UpdateThread(
141outputArea, "\nBUFFER EMPTY" ) );
142}
143
144// tell a waiting thread to become ready
145notify();
146
147 return value;
148
149 } // end method getSharedInt
150
151// diplay contents of shared buffer
152public void displayBuffer( JTextArea outputArea,
153int buffer[] )
154{
155DecimalFormat formatNumber = new DecimalFormat( " #;-#" );
156StringBuffer outputBuffer = new StringBuffer();
Fig. 15.15 Class HoldIntegerSynchronized monitors access to a shared array of integers (part 4 of 5).
Chapter 15 |
Multithreading |
867 |
157
158// place buffer elements in outputBuffer
159for ( int count = 0; count < buffer.length; count++ )
160 |
outputBuffer.append( |
161 |
" " + formatNumber.format( buffer[ count ] ) ); |
162 |
|
163// update Swing GUI component
164SwingUtilities.invokeLater( new UpdateThread( outputArea,
165"\tbuffer: " + outputBuffer ) );
166 |
|
} |
167 |
|
|
168 |
} |
// end class HoldIntegerSynchronized |
Fig. 15.15 Class HoldIntegerSynchronized monitors access to a shared array of integers (part 5 of 5).
Method setSharedInt (lines 34–87) performs the same tasks as it did in Fig. 15.10, with a few modifications. When execution continues at line 55 after the while loop, setSharedInt places the produced value in the circular buffer at location writeLocation. Next, readable is set to true (line 58), because there is at least one value in the buffer that the client can read. Lines 61–63 use SwingUtilities method invokeLater to append the value produced and the cell where the value was placed to the JTextArea (method run of class UpdateThread performs the actual append operation). Then, line 66 updates writeLocation for the next call to setSharedInt. The output continues with the current writeLocation and readLocation values and the values in the circular buffer (lines 69–73). If the writeLocation is equal to the readLocation, the circular buffer is currently full, so writeable is set to false (line 77) and the program displays the string BUFFER FULL (lines 80–81). Finally, line 85 invokes method notify to indicate that a waiting thread should move to the ready state.
Method getSharedInt (lines 92–149) also performs the same tasks in this example as it did in Fig. 15.10, with a few minor modifications. When execution continues at line 115 after the while loop, writeable is set to true because there will be at least one open position in the buffer in which the producer can place a value. Next, line 118 assigns value the value at readLocation in the circular buffer. Lines 121–123 append to the JTextArea the value consumed and the cell from which the value was read. Then, line 126 updates readLocation for the next call to method getSharedInt. Lines 129– 131 continue the output in the JTextArea with the current writeLocation and readLocation values and the current values in the circular buffer. If the readLocation is equal to the writeLocation, the circular buffer is currently empty, so readable is set to false (line 137), and lines 140–141 display the string BUFFER EMPTY. Finally, line 145 invokes method notify to place the next waiting thread into the ready state, and line 147 returns the retrieved value to the calling method.
In this version of the program, the outputs include the current writeLocation and readLocation values and the current contents of the buffer sharedInt. The elements of the sharedInt array were initialized to –1 for output purposes so that you can see each value inserted in the buffer. Notice that after the program places the fifth value in the fifth element of the buffer, the program inserts the sixth value at the beginning of the array— thus providing the circular buffer effect. Method displayBuffer (lines 152–166) uses
868 |
Multithreading |
Chapter 15 |
a DecimalFormat object to format the contents of the array buffer. The format control string " #;-#" indicates a positive number format and a negative number format— the formats are separated by a semicolon (;). The format specifies that positive values should be preceded by a space and negative values should be preceded by a minus sign.
1// Fig. 15.16: SharedCell.java
2 // Show multiple threads modifying shared object.
3
4 // Java core packages
5import java.awt.*;
6import java.awt.event.*;
7 import java.text.DecimalFormat;
8
9 // Java extension packages
10 import javax.swing.*;
11
12 public class SharedCell extends JFrame {
13
14// set up GUI
15public SharedCell()
16{
17super( "Demonstrating Thread Synchronization" );
19JTextArea outputArea = new JTextArea( 20, 30 );
20getContentPane().add( new JScrollPane( outputArea ) );
22setSize( 500, 500 );
23show();
24
25// set up threads
26HoldIntegerSynchronized sharedObject =
27 |
new HoldIntegerSynchronized( outputArea ); |
28 |
|
29 |
ProduceInteger producer = |
30 |
new ProduceInteger( sharedObject, outputArea ); |
31 |
|
32 |
ConsumeInteger consumer = |
33 |
new ConsumeInteger( sharedObject, outputArea ); |
34 |
|
35// start threads
36producer.start();
37consumer.start();
38}
39
40// execute application
41public static void main( String args[] )
42{
43SharedCell application = new SharedCell();
45 application.setDefaultCloseOperation(
46JFrame.EXIT_ON_CLOSE );
47}
Fig. 15.16 Threads modifying a shared array of cells (part 1 of 2).