AhmadLang / Java, How To Program, 2004
.pdf
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
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.
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
