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

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 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
 4 public class SynchronizedBuffer implements Buffer
 5 {
 6 private int buffer = -1; // shared by producer and consumer threads
 7 private boolean occupied = false; // count of occupied buffers
 8
 9 // place value into buffer
10 public synchronized void set( int value )
11 {
12 // while there are no empty locations, place thread in waiting state
13 while ( occupied )
14 {
15 // output thread information and buffer information, then wait
16 try
17 {
18 System.out.println( "Producer tries to write." );
19 displayState( "Buffer full. Producer waits." );
20 wait();
21 } // end try
22 catch ( 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
32 occupied = true; 
33
34 displayState( "Producer writes " + buffer );
35
36 notify(); // tell waiting thread to enter runnable state
37 } // end method set; releases lock on SynchronizedBuffer
38
39 // return value from buffer
40 public synchronized int get()
41 {
42 // while no data to read, place thread in waiting state
43 while ( !occupied )
44 {
45 // output thread information and buffer information, then wait
46 try
47 {
48 System.out.println( "Consumer tries to read." );
49 displayState( "Buffer empty. Consumer waits." );
50 wait();
51 } // end try
52 catch ( 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 
60 occupied = false; 
61
62 int readValue = buffer; // store value in buffer
63 displayState( "Consumer reads " + readValue );
64
65 notify(); // tell waiting thread to enter runnable state
66
67 return readValue;
68 } // end method get; releases lock on SynchronizedBuffer
69
70 // display current operation and buffer state
71 public void displayState( String operation )
72 {
73 System.out.printf( "%-40s%d		%b

", operation, buffer,
74 occupied );
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.
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5
 6 public class SharedBufferTest2
 7 {
 8 public static void main( String[] args )
 9 {
10 // create new thread pool with two threads
11 ExecutorService application = Executors.newFixedThreadPool( 2 );
12
13 // create SynchronizedBuffer to store ints 
14 Buffer sharedLocation = new SynchronizedBuffer();
15
16 System.out.printf( "%-40s%s		%s
%-40s%s

", "Operation",
17 "Buffer", "Occupied", "---------", "------		--------" );
18
19 try // try to start producer and consumer
20 {
21 application.execute( new Producer( sharedLocation ) );
22 application.execute( new Consumer( sharedLocation ) );
23 } // end try
24 catch ( Exception exception )
25 {
26 exception.printStackTrace();
27 } // end catch
28
29 application.shutdown();
30 } // end main
31 } // end class SharedBufferTest2
 
Operation Buffer Occupied
--------- ------ --------

Consumer tries to read.
Buffer empty. Consumer waits. -1 false

Producer writes 1 1 true

Consumer reads 1 1 false

Consumer tries to read.
Buffer empty. Consumer waits. 1 false

Producer writes 2 2 true

Consumer reads 2 2 false

Producer writes 3 3 true

Consumer reads 3 3 false

Consumer tries to read.
Buffer empty. Consumer waits. 3 false

Producer writes 4 4 true

Consumer reads 4 4 false

Consumer tries to read.
Buffer empty. Consumer waits. 4 false

Producer writes 5 5 true

Consumer reads 5 5 false

Producer writes 6 6 true

Consumer reads 6 6 false

Consumer tries to read.
Buffer empty. Consumer waits. 6 false

Producer writes 7 7 true

Consumer reads 7 7 false

Consumer tries to read.
Buffer empty. Consumer waits. 7 false

Producer writes 8 8 true

Consumer reads 8 8 false

Producer writes 9 9 true

Producer tries to write.
Buffer full. Producer waits. 9 true

Consumer reads 9 9 false

Producer writes 10 10 true

Producer done producing.
Terminating Producer.
Consumer reads 10 10 false

Consumer read values totaling 55.
Terminating Consumer.
 

Class SynchronizedBuffer (Fig. 23.19) contains two fieldsbuffer (line 6) and occupied (line 7). Method set (lines 1037) and method get (lines 4068) are declared as synchronized methods by adding the synchronized keyword between the method modifier and the return typethus, only one thread can call any of these methods at a time on a particular SynchronizedBuffer object. Field occupied is used in conditional expressions to determine whether it is the producer's or the consumer's turn to perform a task. If occupied is false, buffer is empty and the producer can call method set to place a value into variable buffer. This condition also means that the consumer cannot call SynchronizedBuffer's get method to read the value of buffer because it is empty. If occupied is TRue, the consumer can call SynchronizedBuffer's get method to read a value from variable buffer, because the variable contains new information. This condition also means that the producer cannot call SynchronizedBuffer's set method to place a value into buffer, because the buffer is currently full.

When the Producer tHRead's run method invokes synchronized method set, the thread attempts to acquire the monitor lock on the SynchronizedBuffer object. If the monitor lock is available, the Producer thread acquires the lock. Then the while loop at lines 1326 determines whether occupied is true. If so, the buffer is full, so line 18 outputs a message indicating that the Producer thread is trying to write a value, and line 19 invokes method displayState (lines 7175) to output another message indicating that the buffer is full and that the Producer thread is in the waiting state. Line 20 invokes method wait (inherited from Object by SynchronizedBuffer) to place the thread that called method set (i.e., the Producer thread) in the waiting state for the SynchronizedBuffer object. The call to wait causes the calling thread to release the lock on the SynchronizedBuffer object. This is important because the thread cannot currently perform its task and because other threads should be allowed to access the object at this time to allow the condition (occupied) to change. Now another thread can attempt to acquire the SynchronizedBuffer object's lock and invoke the object's set or get method.

The producer thread remains in the waiting state until the thread is notified by another thread that it may proceedat which point the producer thread returns to the blocked state and attempts to reacquire the lock on the SynchronizedBuffer object. If the lock is available, the producer thread reacquires the lock, and method set continues executing with the next statement after wait. Because wait is called in a loop (lines 1326), the loop-continuation condition is tested again to determine whether the thread can proceed with its execution. If not, wait is invoked againotherwise, method set continues with the next statement after the loop.

Line 28 in method set assigns value to buffer. Line 32 sets occupied to true to indicate that the buffer now contains a value (i.e., a consumer can read the value, and a producer cannot yet put another value there). Line 34 invokes method displayState to output a line to the console window indicating that the producer is writing a new value into the buffer. Line 36 invokes method notify (inherited from Object). If there are any waiting threads, the first one enters the blocked state, indicating that the thread can now attempt to acquire the lock again. Method notify returns immediately and method set returns to its caller. Invoking method notify works correctly in this program because only one thread calls method get at any time (the ConsumerThread). In programs that have multiple threads waiting on a condition, it may be more appropriate to use method notifyAll or call method wait with an optional timeout. When method set returns, it implicitly releases the lock on the shared memory.

Methods get and set are implemented similarly. When the Consumer thread's run method invokes synchronized method get, the thread attempts to acquire the monitor lock on the SynchronizedBuffer object. If the lock is available, the Consumer thread acquires it. Then the while loop at lines 4356 determines whether occupied is false. If so, the buffer is empty, so line 48 outputs a message indicating that the Consumer thread is trying to read a value, and line 49 invokes method displayState to output another message indicating that the buffer is empty and that the Consumer tHRead is waiting. Line 50 invokes method wait to place the thread that called method get (i.e., the Consumer thread) in the waiting state for the SynchronizedBuffer object. Again, the call to wait causes the calling thread to release the lock on the SynchronizedBuffer object, so another thread can attempt to acquire the SynchronizedBuffer object's lock and invoke the object's set or get method. If the lock on the SynchronizedBuffer is not available (e.g., if the ProducerThread has not yet returned from method set), the ConsumerThread is blocked until the lock becomes available.

The consumer thread object remains in the waiting state until the thread is notified by another thread that it may proceedat which point the consumer thread returns to the blocked state and attempts to reacquire the lock on the SynchronizedBuffer object. If the lock is available, the consumer thread reacquires the lock and method get continues executing with the next statement after wait. Because wait is called in a loop (lines 4356), the loop-continuation condition is tested again to determine whether the thread can proceed with its execution. If not, wait is invoked againotherwise, method get continues with the next statement after the loop. Line 60 sets occupied to false to indicate that buffer is now empty (i.e., a consumer cannot read the value, but a producer can place another value into buffer), line 63 calls method displayState to indicate that the consumer is reading and line 65 invokes method notify. If there are any threads in the blocked state for the lock on this SynchronizedBuffer object, one of them enters the runnable state, indicating that the thread can now attempt to reacquire the lock and continue performing its task. Method notify returns immediately, then method get returns the value of buffer to its caller. Invoking method notify works correctly in this program because only one thread calls method set at any time (the ProducerThread). Programs that have multiple threads waiting on a condition should invoke notifyAll to ensure that multiple threads receive notifications properly. When method get returns, the lock on the SynchronizedBuffer object is implicitly released.

Class SharedBufferTest2 (Fig. 23.20) is identical to class SharedBufferTest (Fig. 23.12). Study the outputs in Fig. 23.20. Observe that every integer produced is consumed exactly onceno values are lost, and no values are consumed more than once. The synchronization and condition variable 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.

Introduction to Computers, the Internet and the World Wide Web

Introduction to Java Applications

Introduction to Classes and Objects

Control Statements: Part I

Control Statements: Part 2

Methods: A Deeper Look

Arrays

Classes and Objects: A Deeper Look

Object-Oriented Programming: Inheritance

Object-Oriented Programming: Polymorphism

GUI Components: Part 1

Graphics and Java 2D™

Exception Handling

Files and Streams

Recursion

Searching and Sorting

Data Structures

Generics

Collections

Introduction to Java Applets

Multimedia: Applets and Applications

GUI Components: Part 2

Multithreading

Networking

Accessing Databases with JDBC

Servlets

JavaServer Pages (JSP)

Formatted Output

Strings, Characters and Regular Expressions

Appendix A. Operator Precedence Chart

Appendix B. ASCII Character Set

Appendix C. Keywords and Reserved Words

Appendix D. Primitive Types

Appendix E. (On CD) Number Systems

Appendix F. (On CD) Unicode®

Appendix G. Using the Java API Documentation

Appendix H. (On CD) Creating Documentation with javadoc

Appendix I. (On CD) Bit Manipulation

Appendix J. (On CD) ATM Case Study Code

Appendix K. (On CD) Labeled break and continue Statements

Appendix L. (On CD) UML 2: Additional Diagram Types

Appendix M. (On CD) Design Patterns

Appendix N. Using the Debugger

Inside Back Cover



Java(c) How to Program
Java How to Program (6th Edition) (How to Program (Deitel))
ISBN: 0131483986
EAN: 2147483647
Year: 2003
Pages: 615

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net