Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Java How to Program, Fourth Edition - Deitel H., Deitel P.pdf
Скачиваний:
58
Добавлен:
24.05.2014
Размер:
14.17 Mб
Скачать

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).