Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
626
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

Consumer

reads 10

Buffers occupied: 0

Consumer

read values totaling 55.

Terminating Consumer.

 

 

 

 

[Page 1086]

Line 19 in method set (lines 1527) calls method put on the ArrayBlockingQueue. This method call will block until there is room in buffer to place value. Method get (lines 3046) of class BlockingBuffer calls method take (line 36) on the ArrayBlockingQueue. Again, this method call will block until there is an element in buffer to remove. Note that neither of these methods requires a Lock or Condition object. The ArrayBlockingQueue handles all of the synchronization for you. The amount of code in this program is greatly decreased from the previous circular buffer (from 123 lines to 47 lines) and is much easier to understand. This is an excellent example of encapsulation and software reuse.

Class BlockingBufferTest (Fig. 23.16) contains the main method that launches the application. Line 11 creates the ExecutorService, and line 14 creates a BlockingBuffer object and assigns its reference to

Buffer variable sharedLocation. Lines 1819 execute the Producer and Consumer Runnables. Line 26 calls method shutdown to end the application when the Producer and Consumer finish.

In our prior synchronization examples, the output statements in the Buffer's set and get methods that indicated what the Producer was writing or the Consumer was reading were always executed while the Buffer's lock was held by the thread calling set or get. This guaranteed the order in which the output would be displayed. If the Consumer had the lock, the Producer could not execute the set methodtherefore, it was not possible for the Producer to output out of turn. The reverse was also true. In Fig. 23.15, methods set and get no longer use locksall locking is handled by the ArrayBlockingQueue. Because this class is from the Java API, we cannot modify it to perform output from its put and take methods. For these reasons, it is possible that the Producer and Consumer output statements in this example could print out of order. Even though the ArrayBlockingQueue is properly synchronizing access to the data, the output statements are no longer synchronized.

[Page 1087]

[Page 1087 (continued)]

23.10. Multithreading with GUI

This program uses separate threads to modify the content displayed in a Swing GUI. The nature of multithreaded programming prevents the programmer from knowing exactly when a thread will execute. Swing components are not thread safeif multiple threads manipulate a Swing GUI component, the results may not be correct. All interactions with Swing GUI components should be performed as part of the event-dispatching thread (also known as the event-handling thread). Class SwingUtilities

(package javax.swing) provides static method invokeLater to help with this process. Method invokeLater specifies GUI processing statements to execute later as part of the event-dispatching thread. Method invokeLater receives as its argument an object that implements interface Runnable. The method places the Runnable as an event into the event-dispatching thread's queue of events. These events are processed in the order they appear in the queue. Because only one thread handles these events, it can be guaranteed that the GUI will be updated properly.

[Page 1088]

Our next example (Fig. 23.17) demonstrates this concept. When this program calls invokeLater, the GUI component update will be queued for execution in the event-dispatching thread. The Runnable's run method will then be invoked as part of the event-dispatching thread to perform the output and ensure that the GUI component is updated in a thread-safe manner. This example also demonstrates how to suspend a thread (i.e., temporarily prevent it from executing) and how to resume a suspended thread.

Figure 23.17. RunnableObject outputs a random uppercase letter on a JLabel. Allows user to suspend and resume execution.

(This item is displayed on pages 1088 - 1090 in the print version)

1 // Fig. 23.17: RunnableObject.java

2 // Runnable that writes a random character to a JLabel

3import java.util.Random;

4import java.util.concurrent.locks.Condition;

5import java.util.concurrent.locks.Lock;

6import javax.swing.JLabel;

7import javax.swing.SwingUtilities;

8import java.awt.Color;

9

10public class RunnableObject implements Runnable

11{

12private static Random generator = new Random(); // for random letters

13private Lock lockObject; // application lock; passed in to constructor

14private Condition suspend; // used to suspend and resume thread

15private boolean suspended = false; // true if thread suspended

16private JLabel output; // JLabel for output

17

18public RunnableObject( Lock theLock, JLabel label )

19{

20lockObject = theLock; // store the Lock for the application

21suspend = lockObject.newCondition(); // create new Condition

22output = label; // store JLabel for outputting character

23} // end RunnableObject constructor

24

25// place random characters in GUI

26public void run()

27{

28// get name of executing thread

29final String threadName = Thread.currentThread().getName();

31 while ( true ) // infinite loop; will be terminated from outside

32{

33try

34{

35

// sleep for up to 1 second

36

Thread.sleep( generator.nextInt( 1000

)

);

37

 

 

 

 

 

 

38

lockObject.lock(); // obtain the lock

 

 

39

try

 

 

 

 

40

{

 

 

 

 

 

41

 

while

( suspended ) // loop until

not

suspended

42

 

{

 

 

 

 

43

 

 

suspend.await(); // suspend thread execution

44

 

}

//

end while

 

 

45

}

//

end

try

 

 

46

finally

 

 

 

47

{

 

 

 

 

 

48

 

lockObject.unlock(); // unlock the lock

49

}

//

end

finally

 

 

50} // end try

51// if thread interrupted during wait/sleep

52catch ( InterruptedException exception )

53{

54

exception.printStackTrace(); // print stack trace

55

} // end catch

56

 

57// display character on corresponding JLabel

58SwingUtilities.invokeLater(

59

new

Runnable()

 

60

{

 

 

 

61

// pick random character and display it

 

62

public

void run()

 

63

{

 

 

 

64

 

// select random uppercase letter

 

65

 

char

displayChar =

 

66

 

(

char ) ( generator.nextInt( 26

) + 65 );

67

 

 

 

 

68

 

// output character in JLabel

 

69

 

output.setText( threadName + ": " +

displayChar );

70

} // end method run

 

71

} //

end inner class

 

72); // end call to SwingUtilities.invokeLater

73} // end while

74} // end method run

75

76// change the suspended/running state

77public void toggle()

78{

79suspended = !suspended; // toggle boolean controlling state

81// change label color on suspend/resume

82output.setBackground( suspended ? Color.RED : Color.GREEN );

84lockObject.lock(); // obtain lock

85try

86{

87if ( !suspended ) // if thread resumed

88{

89

suspend.signal(); // resume thread

90} // end if

91} // end try

92finally

93{

94lockObject.unlock(); // release lock

95} // end finally

96} // end method toggle

97} // end class RunnableObject

Class RunnableObject (Fig. 23.17) implements interface Runnable's run method (lines 2674). Line 29 uses static Thread method currentThread to determine the currently executing thread and Thread method getName to return the name of that thread. Every executing thread has a default name that includes the number of the thread (see the output of Fig. 23.18). Lines 3173 are an infinite loop. [Note: In earlier chapters we have said that infinite loops are bad programming because the application will not terminate. In this case, the infinite loop is in a separate thread from the main thread. When the application window is closed in this example, all the threads created by the main thread are closed as

well, including threads (such as this one) that are executing infinite loops.] In each iteration of the loop, the thread sleeps for a random interval from 0 to 1 seconds (line 36).

Figure 23.18. RandomCharacters creates a JFrame with three

RunnableObjects and three JCheckBoxes to allow the user to suspend and resume threads.

(This item is displayed on pages 1091 - 1093 in the print version)

1 // Fig. 23.18: RandomCharacters.java

2 // Class RandomCharacters demonstrates the Runnable interface

3import java.awt.Color;

4import java.awt.GridLayout;

5import java.awt.event.ActionEvent;

6import java.awt.event.ActionListener;

7import java.util.concurrent.Executors;

8import java.util.concurrent.ExecutorService;

9import java.util.concurrent.locks.Condition;

10import java.util.concurrent.locks.Lock;

11import java.util.concurrent.locks.ReentrantLock;

12import javax.swing.JCheckBox;

13import javax.swing.JFrame;

14import javax.swing.JLabel;

16public class RandomCharacters extends JFrame implements ActionListener

17{

18private final static int SIZE = 3; // number of threads

19private JCheckBox checkboxes[]; // array of JCheckBoxes

20private Lock lockObject = new ReentrantLock( true ); // single lock

22// array of RunnableObjects to display random characters

23private RunnableObject[] randomCharacters =

24new RunnableObject[ SIZE ];

26// set up GUI and arrays

27public RandomCharacters()

28{

29checkboxes = new JCheckBox[ SIZE ]; // allocate space for array

30

setLayout( new GridLayout( SIZE, 2, 5, 5 ) ); // set layout

31

 

32// create new thread pool with SIZE threads

33ExecutorService runner = Executors.newFixedThreadPool( SIZE );

35// loop SIZE times

36 for ( int count = 0; count < SIZE; count++ )

37{

38JLabel outputJLabel = new JLabel(); // create JLabel

39outputJLabel.setBackground( Color.GREEN ); // set color

40outputJLabel.setOpaque( true ); // set JLabel to be opaque

41add( outputJLabel ); // add JLabel to JFrame

42

43// create JCheckBox to control suspend/resume state

44checkboxes[ count ] = new JCheckBox( "Suspended" );

46// add listener which executes when JCheckBox is clicked

47checkboxes[ count ].addActionListener( this );

48add( checkboxes[ count ] ); // add JCheckBox to JFrame

50// create a new RunnableObject

51randomCharacters[ count ] =

52

new RunnableObject( lockObject, outputJLabel );

53

 

54// execute RunnableObject

55runner.execute( randomCharacters[ count ] );

56} // end for

57

58setSize( 275, 90 ); // set size of window

59setVisible( true ); // show window

60

61runner.shutdown(); // shutdown runner when threads finish

62} // end RandomCharacters constructor

63

64// handle JCheckBox events

65public void actionPerformed( ActionEvent event )

66{

67// loop over all JCheckBoxes in array

68for ( int count = 0; count < checkboxes.length; count++ )

69{

70// check if this JCheckBox was source of event

71if ( event.getSource() == checkboxes[ count ] )

72

randomCharacters[ count ].toggle(); // toggle state

73} // end for

74} // end method actionPerformed

76public static void main( String args[] )

77{

78// create new RandomCharacters object

79RandomCharacters application = new RandomCharacters();

81// set application to end when window is closed

82application.setDefaultCloseOperation( EXIT_ON_CLOSE );

83} // end main

84} // end class RandomCharacters

[View full size image]

When the thread awakens, line 38 acquires the Lock on this application. Lines 4144 loop while the boolean variable suspended is TRue. Line 43 calls method await on Condition suspend to temporarily release the Lock and place this thread into the waiting state. When this thread is signaled, it reacquires the Lock, moves back to the runnable state and releases the Lock (line 48). When suspended is false, the thread should resume execution. If suspended is still true, the loop executes again.

[Page 1090]

Lines 5872 call SwingUtilities method invokeLater. Lines 5971 declare an anonymous inner class that implements the Runnable interface, and lines 6270 declare method run. The method call to invokeLater places this Runnable object in a queue to be executed by the event-dispatching thread. Lines 6566 create a random uppercase character. Line 69 calls method setText on the JLabel output to display the thread name and the random character on the JLabel in the application window.

When the user clicks the JCheckBox to the right of a particular JLabel, the corresponding thread should be suspended (temporarily prevented from executing) or resumed (allowed to continue executing). Suspending and resuming of a thread can be implemented by using thread synchronization and Condition methods await and signal. Lines 7796 declare method toggle, which will change the suspended/resumed state of the current thread. Line 79 reverses the value of boolean variable suspended. Line 82 changes the background color of the JLabel by calling method setBackground. If the thread is suspended, the background color will be Color.RED, whereas if the thread is running, the background color will be Color.GREEN. Method toggle is called from the event handler in Fig. 23.18, so its tasks will be performed in the event-dispatch threadthus, there is no need to use invokeLater for line 82. Line 84 acquires the Lock for this application. Line 87 then tests whether the thread was just resumed. If this is true, line 89 calls method signal on Condition suspend. This method call will alert a thread that was placed in the waiting state by the await method call in line 43. Line 94 releases the Lock on this application inside a finally block.

Note that the if statement in line 87 does not have an associated else. If this condition fails, it means that the thread has just been suspended. When this happens, a thread executing at line 38 will enter the while loop and line 43 will suspend the thread with a call to method await.

[Page 1091]

Class RandomCharacters (Fig. 23.18) displays three JLabels and three JCheckBoxes. A separate thread of execution is associated with each JLabel and JCheckBox pair. Each thread randomly displays letters from the alphabet in its corresponding JLabel object. Line 33 creates a new ExecutorService with method newFixedThreadPool. Lines 3656 iterate three times. Lines 3841 create and customize the JLabel. Line 44 creates the JCheckBox, and line 47 adds an ActionListener for each JCheckBox (this will be discussed later.) Lines 5152 create a new RunnableObject implements the Runnable interface. Line 55 executes the RunnableObject with one of the threads of execution created in runner in line 33.

If the user clicks the Suspended checkbox next to a particular JLabel, the program invokes method actionPerformed (lines 6574) to determine which checkbox generated the event. Lines 6873 determine which checkbox generated the event. Line 71 checks whether the source of the event is the JCheckBox in the current index. If it is, line 72 calls method toggle (lines 7592 of Fig. 23.17) on that RunnableObject.

[Page 1093]

[Page 1093 (continued)]

23.11. Other Classes and Interfaces in java.util.concurrent

The Runnable interface provides only the most basic functionality for multithreaded programming. In fact, this interface has several limitations. Suppose a Runnable encounters a problem and tries to throw a checked exception. The run method is not declared to throw any exceptions, so the problem must be handled within the Runnablethe exception cannot be passed to the calling thread. Now suppose a Runnable is performing a long calculation and the application wants to retrieve the result of that calculation. The run method cannot return a value, so the application must use shared data to pass the value back to the calling thread. This also involves the overhead of synchronizing access to the data. The developers of the new concurrency APIs in J2SE 5.0 recognized these limitations and created a new interface to fix them. The Callable interface (package java.util.concurrent) declares a single method named call. This interface is designed to be similar to the Runnable interfaceallowing an action to be performed concurrently in a separate threadbut the call method allows the thread to return a value or to throw a checked exception.

An application that creates a Callable likely wants to run the Callable concurrently with other Runnables and Callables. The ExecutorService interface provides method submit, which will execute a Callable passed in as its argument. The submit method returns an object of type Future (package java.util.concurrent), which is an interface that represents the executing Callable. The Future interface declares method get to return the result of the Callable and provides other methods to manage a Callable's execution.

[Page 1093 (continued)]

23.12. Monitors and Monitor Locks

Another way to perform synchronization is to use Java's built-in monitors. Every object has a monitor. The monitor allows one thread at a time to execute inside a synchronized statement on the object. This is accomplished by acquiring a lock on the object when the program enters the synchronized statement. These statements are declared using the synchronized keyword with the form

synchronized ( object )

{

statements

} // end synchronized statement

[Page 1094]

where object is the object whose monitor lock will be acquired. If there are several synchronized statements trying to execute on an object at the same time, only one of them may be active on the object at onceall the other threads attempting to enter a synchronized statement on the same object are placed in the blocked state.

The blocked state is not included in Fig. 23.1, but it transitions to and from the runnable state. When a runnable thread must wait to enter a synchronized statement, it transitions to the blocked state. When the blocked thread enters the synchronized statement, it transitions to the runnable state.

When a synchronized statement finishes executing, the monitor lock on the object is released and the highest-priority blocked thread attempting to enter a synchronized statement proceeds. Java also allows synchronized methods. A synchronized method is equivalent to a synchronized statement enclosing the entire body of a method.

If a thread obtains the monitor lock on an object and then determines that it cannot continue with its task on that object until some condition is satisfied, the thread can call Object method wait, releasing the monitor lock on the object. The thread releases the monitor lock on the object and waits in the waiting state while the other threads try to enter the object's synchronized statement(s). When a thread executing a synchronized statement completes or satisfies the condition on which another thread may be waiting, it can call Object method notify to allow a waiting thread to transition to the blocked state again. At this point, the thread that transitioned from the wait state to the blocked state can attempt to reacquire the monitor lock on the object. Even if the thread is able to reacquire the monitor lock, it still might not be able to perform its task at this timein which case the thread will reenter the waiting state and release the monitor lock. If a thread calls notifyAll, then all the threads waiting for the monitor lock become eligible to reacquire the lock (that is, they all transition to the blocked state). Remember that only one thread at a time can obtain the monitor lock on the objectother threads that attempt to acquire the same monitor lock will be blocked until the monitor lock becomes available again (i.e., until no other thread is executing in a synchronized statement on that object). Methods wait, notify and notifyAll are inherited by all classes from class Object.

Software Engineering Observation 23.3

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 locks and avoid these kinds of deadlocks.

Common Programming Error 23.5

It is an error if a thread issues a wait, a notify or a notifyAll on an

// while there are no empty locations, place thread in waiting state
private int buffer = -1; // shared by producer and consumer threads private boolean occupied = false; // count of occupied buffers

object without having acquired a lock for it. This causes an

IllegalMonitorStateException.

The application in Fig. 23.19 and Fig. 23.20 demonstrates a producer and a consumer accessing a shared buffer with synchronization. In this case, the consumer consumes only after the producer produces a value, and the producer produces a new value only after the consumer consumes the value produced previously. In this example, we reuse interface Buffer (Fig. 23.6) and classes Producer (Fig. 23.7) and Consumer (Fig. 23.8) from the example in Section 23.6. The code that performs the synchronization is placed in the set and get methods of class SynchronizedBuffer (Fig. 23.19), which implements interface Buffer (line 4). Thus, the Producer's and Consumer's run methods simply call the shared object's set and get methods, as in the example in Section 23.6.

Figure 23.19. Synchronizes access to shared data using Object methods wait and notify.

(This item is displayed on pages 1095 - 1096 in the print version)

1

//

Fig. 23.19: SynchronizedBuffer.java

2

//

SynchronizedBuffer synchronizes access to a single shared integer.

3

 

 

4public class SynchronizedBuffer implements Buffer

5{

6

7

8

9// place value into buffer

10public synchronized void set( int value )

11{

12

13while ( occupied )

14{

15// output thread information and buffer information, then wait

16try

17{

18

System.out.println( "Producer tries to write." );

19

displayState( "Buffer full. Producer waits." );

20

wait();

21} // end try

22catch ( InterruptedException exception )

23{

24

exception.printStackTrace();

25} // end catch

26} // end while

27

28 buffer = value; // set new buffer value

29

30// indicate producer cannot store another value

31// until consumer retrieves current buffer value

32occupied = true;

33

34 displayState( "Producer writes " + buffer );

35

36notify(); // tell waiting thread to enter runnable state

37} // end method set; releases lock on SynchronizedBuffer

38

39// return value from buffer

40public synchronized int get()

41{

42// while no data to read, place thread in waiting state

43while ( !occupied )

44{

45// output thread information and buffer information, then wait

46try

47{

48

System.out.println( "Consumer tries to read." );

49

displayState( "Buffer empty. Consumer waits." );

50

wait();

51} // end try

52catch ( InterruptedException exception )

53{

54

exception.printStackTrace();

55} // end catch

56} // end while

57

58// indicate that producer can store another value

59// because consumer just retrieved buffer value

60occupied = false;

61

62int readValue = buffer; // store value in buffer

63displayState( "Consumer reads " + readValue );

65notify(); // tell waiting thread to enter runnable state

67return readValue;

68} // end method get; releases lock on SynchronizedBuffer

70// display current operation and buffer state

71public void displayState( String operation )

72{

73System.out.printf( "%-40s%d\t\t%b\n\n", operation, buffer,

74occupied );

75} // end method displayState

76} // end class SynchronizedBuffer

Figure 23.20. SharedBufferTest2 sets up a producer/consumer application that uses a synchronized buffer.

(This item is displayed on pages 1098 - 1100 in the print version)

1 // Fig 23.20: SharedBufferTest2.java

2 // Application shows two threads manipulating a synchronized buffer.

3import java.util.concurrent.ExecutorService;

4import java.util.concurrent.Executors;

5

6public class SharedBufferTest2

7{

8public static void main( String[] args )

9{

10// create new thread pool with two threads

11ExecutorService application = Executors.newFixedThreadPool( 2 );

13// create SynchronizedBuffer to store ints

14Buffer sharedLocation = new SynchronizedBuffer();

16System.out.printf( "%-40s%s\t\t%s\n%-40s%s\n\n", "Operation",

17

"Buffer", "Occupied", "---------

", "------

\t\t--------

" );

18

 

 

 

 

19try // try to start producer and consumer

20{

21application.execute( new Producer( sharedLocation ) );

22application.execute( new Consumer( sharedLocation ) );

23} // end try

24catch ( Exception exception )

25{

26exception.printStackTrace();

27} // end catch

28

29application.shutdown();

30} // end main

31} // end class SharedBufferTest2