AhmadLang / Java, How To Program, 2004
.pdf
variable canRead. Recall that method signal is called on variable canRead in the set method (line 44).
When the condition variable is signaled, the get method continues. Line 75 sets occupied to false, line 77 stores the value of buffer in readValue and line 78 outputs the readValue. Then line 81 signals the condition variable canWrite. This will awaken the Producer if it is indeed waiting for the buffer to be emptied. Line 90 calls method unlock in a finally block to release the lock, and line 93 returns the value of the buffer to the calling method.
Software Engineering Observation 23.2
Always invoke method await in a loop that tests an appropriate condition. It is possible that a thread will reenter the runnable state before the condition it was waiting on is satisfied. Testing the condition again ensures that the thread will not erroneously execute if it was signaled early.
Common Programming Error 23.4
Forgetting to signal a thread that is waiting for a condition is a logic error. The thread will remain in the waiting state, which will prevent the thread from doing any further work. Such waiting can lead to indefinite postponement or deadlock.
Class SharedBufferTest2 (Fig. 23.12) is similar to class SharedBufferTest (Fig. 23.10).
SharedBufferTest2 contains method main (lines 830), which launches the application. Line 11 creates an ExecutorService with two threads to run the Producer and Consumer. Line 14 creates a SynchronizedBuffer object and assigns its reference to Buffer variable sharedLocation. This object stores the data that will be shared between the Producer and Consumer tHReads. Lines 1617 display the column heads for the output. Lines 2122 execute a Producer and a Consumer. Finally, line 29 calls method shutdown to end the application when the Producer and Consumer complete their tasks. When method main ends (line 30), the main thread of execution terminates.
Study the outputs in Fig. 23.12. Observe that every integer produced is consumed exactly onceno values are lost, and no values are consumed more than once. The synchronization and condition variables ensure that the Producer and Consumer cannot perform their tasks unless it is their turn. The Producer must go first, the Consumer must wait if the Producer has not produced since the Consumer last consumed, and the Producer must wait if the Consumer has not yet consumed the value that the Producer most recently produced. Execute this program several times to confirm that every integer produced is consumed exactly once. In the sample output, note the lines indicating when the Producer and Consumer must wait to perform their respective tasks.
26application.shutdown();
27} // end main
28} // end class CircularBufferTest
Producer writes 1 (buffers occupied: 1)
buffers: |
1 |
-1 |
-1 |
|
---- |
---- |
---- |
|
R |
W |
|
Consumer |
reads 1 (buffers occupied: 0) |
||
buffers: |
1 |
-1 |
-1 |
|
---- |
---- |
---- |
|
|
WR |
|
All buffers empty. Consumer waits. Producer writes 2 (buffers occupied: 1)
buffers: |
1 |
2 |
-1 |
|
---- |
---- |
---- |
|
|
R |
W |
Consumer reads 2 (buffers occupied: 0)
buffers: |
1 |
2 |
-1 |
|
---- |
---- |
---- |
|
|
|
WR |
Producer writes 3 (buffers occupied: 1)
buffers: |
1 |
2 |
3 |
|
---- |
---- |
---- |
|
W |
|
R |
Consumer reads 3 (buffers occupied: 0)
buffers: |
1 |
2 |
3 |
|
---- |
---- |
---- |
|
WR |
|
|
Producer writes 4 (buffers occupied: 1)
buffers: |
4 |
2 |
3 |
|
---- |
---- |
---- |
|
R |
W |
|
Producer writes 5 (buffers occupied: 2)
buffers: |
4 |
5 |
3 |
|
---- |
---- |
---- |
|
R |
|
W |
Consumer reads 4 (buffers occupied: 1)
buffers: |
4 |
5 |
3 |
|
---- |
---- |
---- |
|
|
R |
W |
Producer writes 6 (buffers occupied: 2)
buffers: |
4 |
5 |
6 |
|
---- |
---- |
---- |
|
W |
R |
|
Producer writes 7 (buffers occupied: 3)
buffers: |
7 |
5 |
6 |
|
---- |
---- |
---- |
|
|
WR |
|
Consumer reads 5 (buffers occupied: 2)
buffers: |
7 |
5 |
6 |
|
---- |
---- |
---- |
|
|
W |
R |
Producer writes 8 (buffers occupied: 3)
buffers: |
7 |
8 |
6 |
|
---- |
---- |
---- |
|
|
|
WR |
Consumer |
reads 6 |
(buffers occupied: 2) |
||
buffers: |
7 |
|
8 |
6 |
|
---- |
---- |
---- |
|
|
R |
|
|
W |
Consumer |
reads 7 |
(buffers occupied: 1) |
||
buffers: |
7 |
|
8 |
6 |
|
---- |
---- |
---- |
|
|
|
|
R |
W |
Producer writes 9 (buffers occupied: 2)
buffers: |
7 |
8 |
9 |
|
---- |
---- |
---- |
|
W |
R |
|
Consumer reads 8 (buffers occupied: 1) |
|||
buffers: |
7 |
8 |
9 |
|
---- |
---- |
---- |
|
W |
|
R |
Consumer reads 9 (buffers occupied: 0) |
|||
buffers: |
7 |
8 |
9 |
|
---- |
---- |
---- |
|
WR |
|
|
Producer |
writes 10 |
(buffers occupied: 1) |
|
buffers: |
10 |
8 |
9 |
|
---- |
---- |
---- |
|
R |
W |
|
Producer |
done |
producing. |
|
Terminating Producer. |
|||
Consumer |
reads 10 |
(buffers occupied: 0) |
|
buffers: |
10 |
8 |
9 |
|
---- |
---- |
---- |
|
|
WR |
|
Consumer |
read |
values totaling: 55. |
|
Terminating Consumer.
[Page 1080]
The significant changes to the example in Section 23.7 occur in CircularBuffer (Fig. 23.13), which replaces SynchronizedBuffer (Fig. 23.11). Line 10 creates a new ReentrantLock object and assigns its reference to Lock variable accessLock. The ReentrantLock is created without a fairness policy because we have only two threads in this example and only one will ever be waiting. Lines 1314 create two
Conditions using Lock method newCondition. Condition canWrite contains a queue for threads waiting while the buffer is full. If the buffer is full, the Producer calls method await on this Conditionwhen the Consumer frees space in a full buffer, it calls method signal on this Condition. Condition canRead contains a queue for threads waiting while the buffer is empty. If the buffer is empty, the Consumer calls method await on this Conditionwhen the Producer writes to the buffer, it calls method signal on this Condition. Array buffer (line 16) is a three-element integer array that represents the circular buffer. Variable occupiedBuffers (line 18) counts the number of elements in buffer that are filled with data available to be read. When occupiedBuffers is 0, there is no data in the circular buffer and the Consumer must waitwhen occupiedBuffers is 3 (the size of the circular buffer), the circular buffer is full and the Producer must wait. Variable writeIndex (line 19) indicates the next location in which a value can be placed by a Producer. Variable readIndex (line 20) indicates the position from which the next value can be read by a Consumer.
CircularBuffer method set (lines 2354) performs the same tasks that it did in Fig. 23.11, with a few modifications. The while loop at lines 3135 determines whether the Producer must wait (i.e., all buffers are full). If so, line 33 indicates that the Producer is waiting to perform its task. Then line 34 invokes Condition method await to place the Producer tHRead in the waiting state on the canWrite condition variable. When execution eventually continues at line 37 after the while loop, the value written by the Producer is placed in the circular buffer at location writeIndex. Then line 40 updates writeIndex for the next call to CircularBuffer method set. This line is the key to the circularity of the buffer. When writeIndex is incremented past the end of the buffer, this line sets it to 0. Line 42 increments occupiedBuffers, because there is now at least one value in the buffer that the Consumer can read. Next, line 43 invokes method displayState to update the output with the value produced, the number of occupied buffers, the contents of the buffers and the current writeIndex and readIndex. Line 44 invokes Condition method signal to indicate that a Consumer thread waiting on the canRead condition variable (if there is a waiting thread) should transition to the runnable state. Line 52 releases accessLock by calling method unlock inside a finally block.
[Page 1081]
Method get (lines 5792) of class CircularBuffer also performs the same tasks as it did in Fig. 23.11, with a few minor modifications. The while loop at lines 6670 determines whether the Consumer must wait (i.e., all buffers are empty). If the Consumer thread must wait, line 68 updates the output to indicate that the Consumer is waiting to perform its task. Then line 69 invokes Condition method await to place the current thread in the waiting state on the canRead condition variable. When execution eventually
continues at line 72 after a signal call from the Producer, readValue is assigned the value at location readIndex in the circular buffer. Then line 75 updates readIndex for the next call to CircularBuffer method get. This line and line 40 create the circular effect of the buffer. Line 77 decrements the occupiedBuffers, because there is at least one open position in the buffer in which the Producer thread can place a value. Line 78 invokes method displayState to update the output with the consumed value, the number of occupied buffers, the contents of the buffers and the current writeIndex and readIndex. Line 79 invokes Condition method signal to transition the thread waiting to write into the CircularBuffer object into the runnable state. Line 88 releases accessLock inside a finally block to guarantee that the lock is released. Then line 91 returns the consumed value to the calling method.
Method displayState (lines 95122) outputs the state of the application. Lines 101102 output the current buffers. Line 102 uses method printf with a %2d format specifier to print the contents of each buffer with a leading space if it is a single digit. Lines 109119 output the current writeIndex and readIndex with the letters W and R respectively.
Class CircularBufferTest (Fig. 23.14) contains the main method that launches the application. Line 11 creates the ExecutorService with two threads, and line 14 creates a CircularBuffer object and assigns its reference to Buffer variable sharedLocation. Lines 1819 execute the Producer and Consumer. Line 26 calls method shutdown to end the application when the Producer and Consumer complete their tasks.
[Page 1084]
Each time the Producer writes a value or the Consumer reads a value, the program outputs the action performed (a read or a write) along with the contents of the buffer and the location of the write index and read index. In this output, the Producer first writes the value 1. The buffer then contains the value 1 in the first slot and the value -1 (the default value) in the other two slots. The write index is updated to the second slot, while the read index stays at the first slot. Next, the Consumer reads 1. The buffer contains the same values, but the read index has been updated to the second slot. The Consumer then tries to read again, but the buffer is empty and the Consumer is forced to wait. Note that only once in this execution of the program was it necessary for either thread to wait.
39} // end try
40catch ( Exception exception )
41{
42exception.printStackTrace();
43} // end catch
44
45return readValue;
46} // end method get
47} // end class BlockingBuffer
Figure 23.16. BlockingBufferTest sets up a producer/consumer application using a blocking circular buffer.
(This item is displayed on pages 1086 - 1087 in the print version)
1 // Fig 23.16: BlockingBufferTest.java
2 // Application shows two threads manipulating a blocking buffer.
3import java.util.concurrent.ExecutorService;
4import java.util.concurrent.Executors;
5
6public class BlockingBufferTest
7{
8public static void main( String[] args )
9{
10// create new thread pool with two threads
11ExecutorService application = Executors.newFixedThreadPool( 2 );
13// create BlockingBuffer to store ints
14Buffer sharedLocation = new BlockingBuffer();
16try // try to start producer and consumer
17{
18application.execute( new Producer( sharedLocation ) );
19application.execute( new Consumer( sharedLocation ) );
20} // end try
21catch ( Exception exception )
22{
23exception.printStackTrace();
24} // end catch
25
26application.shutdown();
27} // end main
28} // end class BlockingBufferTest
Producer |
writes |
1 |
Buffers |
occupied: |
1 |
Consumer |
reads |
1 |
Buffers |
occupied: |
0 |
Producer |
writes |
2 |
Buffers |
occupied: |
1 |
Consumer |
reads |
2 |
Buffers |
occupied: |
0 |
Producer |
writes |
3 |
Buffers |
occupied: |
1 |
Consumer |
reads |
3 |
Buffers |
occupied: |
0 |
Producer |
writes |
4 |
Buffers |
occupied: |
1 |
Consumer |
reads |
4 |
Buffers |
occupied: |
0 |
Producer |
writes |
5 |
Buffers |
occupied: |
1 |
Consumer |
reads |
5 |
Buffers |
occupied: |
0 |
Producer |
writes |
6 |
Buffers |
occupied: |
1 |
Consumer |
reads |
6 |
Buffers |
occupied: |
0 |
Producer |
writes |
7 |
Buffers |
occupied: |
1 |
Producer |
writes |
8 |
Buffers |
occupied: |
2 |
Consumer |
reads |
7 |
Buffers |
occupied: |
1 |
Producer |
writes |
9 |
Buffers |
occupied: |
2 |
Consumer |
reads |
8 |
Buffers |
occupied: |
1 |
Producer |
writes |
10 |
Buffers |
occupied: |
2 |
Producer |
done producing. |
|
|
|
|
Terminating Producer. |
|
|
|
||
Consumer |
reads |
9 |
Buffers |
occupied: |
1 |
