27.5. Creating Custom Source Components

 
[Page 792 ( continued )]

24.11. (Optional) Cooperation Among Threads

Thread synchronization suffices to avoid race conditions by ensuring the mutual exclusion of multiple threads in the critical region, but sometimes you also need a way for threads to cooperate. Conditions can be used to facilitate communications among threads. A thread can specify what to do under a certain condition. Conditions are objects created by invoking the newCondition() method on a Lock object. Once a condition is created, you can use its await() , signal() , and signalAll() methods for thread communications, as shown in Figure 24.15. The await() method causes the current thread to wait until the condition is signaled. The signal() method wakes up one waiting thread, and the signalAll() method wakes all waiting threads.


[Page 793]
Figure 24.15. The Condition interface defines the methods for performing synchronization.

Let us use an example to demonstrate thread communications. Suppose that you create and launch two tasks , one that deposits to an account, and one that withdraws from the same account. The withdraw task has to wait if the amount to be withdrawn is more than the current balance in the account. Whenever new funds are deposited to the account, the deposit task notifies the withdraw thread to resume. If the amount is still not enough for a withdrawal, the withdraw thread has to continue to wait for a new deposit.

To synchronize the operations, use a lock with a condition: newDeposit (i.e., a new deposit added to the account). If the balance is less than the amount to be withdrawn, the withdraw task will wait for the newDeposit condition. When the deposit task adds money to the account, the task signals the waiting withdraw task to try again. The interaction between the two tasks is shown in Figure 24.16.

Figure 24.16. The condition newDeposit is used for communications between the two threads.


You create a condition from a Lock object. To use a condition, you have to first obtain a lock. The await() method causes the thread to wait and automatically releases the lock on the condition. Once the condition is right, the thread reacquires the lock and continues executing.

Assume that the initial balance is and the amounts to deposit and withdraw are randomly generated. Listing 24.9 gives the program. A sample run of the program is shown in Figure 24.17.


[Page 794]
Figure 24.17. Withdraw task waits if there are not sufficient funds to withdraw.


Listing 24.9. ThreadCooperation.java
(This item is displayed on pages 794 - 795 in the print version)
 1   import   java.util.concurrent.*; 2   import   java.util.concurrent.locks.*; 3 4   public class   ThreadCooperation { 5   private static   Account account =   new   Account(); 6 7   public static void   main(String[] args) { 8  // Create a thread pool with two threads  9  ExecutorService executor = Executors.newFixedThreadPool(   2   );  10 executor.execute(   new   DepositTask()); 11 executor.execute(   new   WithdrawTask()); 12 executor.shutdown(); 13 14 System.out.println(   "Thread 1\t\tThread 2\t\tBalance"   ); 15 } 16 17  // A task for adding an amount to the account  18    public static class   DepositTask   implements   Runnable  { 19    public void   run()  { 20   try   {  // Purposely delay it to let the withdraw method proceed  21   while   (   true   ) { 22 account.deposit((   int   )(Math.random() *   10   ) +   1   ); 23 Thread.sleep(   1000   ); 24 } 25 } 26   catch   (InterruptedException ex) { 27 ex.printStackTrace(); 28 } 29 } 30 } 31 32  // A task for subtracting an amount from the account  33    public static class   WithdrawTask   implements   Runnable  { 34    public void   run()  { 35   while   (   true   ) { 36 account.withdraw((   int   )(Math.random() *   10   ) +   1   ); 37 } 38 } 39 } 40 41  // An inner class for account  42   private static class   Account { 43  // Create a new lock  44    private static   Lock lock =   new   ReentrantLock();  45 

[Page 795]
 46  // Create a condition  47    private static   Condition newDeposit = lock.newCondition();  48 49   private int   balance =     ; 50 51   public int   getBalance() { 52   return   balance; 53 } 54 55   public void   withdraw(   int   amount) { 56  lock.lock();  // Acquire the lock   57   try   { 58   while   (balance < amount) 59  newDeposit.await();  60 61 balance -= amount; 62 System.out.println(   "\t\t\tWithdraw "   + amount + 63   "\t\t"   + getBalance()); 64 } 65   catch   (InterruptedException ex) { 66 ex.printStackTrace(); 67 } 68   finally   { 69  lock.unlock();  // Release the lock   70 } 71 } 72 73   public void   deposit(   int   amount) { 74  lock.lock();  // Acquire the lock   75   try   { 76 balance += amount; 77 System.out.println(   "Deposit "   + amount + 78   "\t\t\t\t\t"   + getBalance()); 79 80  // Signal thread waiting on the condition  81  newDeposit.signalAll();  82 } 83   finally   { 84  lock.unlock();  // Release the lock   85 } 86 } 87 } 88 } 

The example creates a new inner class named Account to model the account with two methods, deposit(int) and withdraw(int) , a class named DepositTask to add an amount to the balance, a class named WithdrawTask to withdraw an amount from the balance, and a main class that creates and launches two threads.

The program creates and submits the deposit task (line 10) and the withdraw task (line 11). The deposit task is purposely put to sleep (line 23) to let the withdraw task run. When there are not enough funds to withdraw, the withdraw task waits (line 59) for notification of the balance change from the deposit task (line 81).

A lock is created in line 44. A condition named newDeposit on the lock is created in line 47. A condition is bound to a lock. Before waiting or signaling the condition, a thread must first acquire the lock for the condition. The withdraw task acquires the lock in line 56, waits for the newDeposit condition (line 59) when there is not a sufficient amount to withdraw, and releases the lock in line 69. The deposit task acquires the lock in line 74, and signals all waiting threads (line 81) for the newDeposit condition after a new deposit is made.


[Page 796]

What will happen if you replace the while loop in lines 58 “59 with the following if statement?

   if   (balance < amount) newDeposit.await(); 

The deposit task will notify the withdraw task whenever the balance changes. (balance < amount) may still be true when the withdraw task is awakened. Using the loop statement, the withdraw task will have a chance to recheck the condition. But using the if statement will not recheck the condition.

Caution

Once a thread invokes await() on a condition, the thread is put to wait for a signal to resume. If you forget to call signal() or signalAll() on the condition, the thread will wait forever.


Caution

A condition is created from a Lock object. To invoke any method (e.g., await() , signal() , and signalAll() ), you must first own the lock. If you invoke these methods without acquiring the lock, an IllegalMonitorStateException will be thrown.


24.11.1. (Optional) Java's Built-In Monitor

Locks and conditions are new in Java 5. Prior to Java 5, thread communications were programmed using the object's built-in monitors . Locks and conditions are more powerful and flexible than the built-in monitor, and in consequence, this section can be completely ignored. However, if you are working with legacy Java code, you may encounter Java's built-in monitor.

A monitor is an object with mutual exclusion and synchronization capabilities. Only one thread can execute a method at a time in the monitor. A thread enters the monitor by acquiring a lock on the monitor and exits by releasing the lock. Any object can be a monitor . An object becomes a monitor once a thread locks it. Locking is implemented using the synchronized keyword on a method or a block. A thread must acquire a lock before executing a synchronized method or block. A thread can wait in a monitor if the condition is not right for it to continue executing in the monitor. You can invoke the wait() method on the monitor object to release the lock so that some other thread can get in the monitor and perhaps change the state of the monitor. When the condition is right, the other thread can invoke the notify() or notifyAll() method to signal one or all waiting threads to regain the lock and resume execution. The template for invoking these methods is shown in Figure 24.18.

Figure 24.18. The wait() , notify() , and notifyAll() methods coordinate thread communication.


[Page 797]

The wait() , notify() , and notifyAll() methods must be called in a synchronized method or a synchronized block on the receiving object of these methods. Otherwise, an IllegalMonitorStateException will occur.

When wait() is invoked, it pauses the thread and simultaneously releases the lock on the object. When the thread is restarted after being notified, the lock is automatically reacquired.

The wait() , notify() , and notifyAll() methods on an object are analogous to the await() , signal() , and signalAll() methods on a condition.

 


Introduction to Java Programming-Comprehensive Version
Introduction to Java Programming-Comprehensive Version (6th Edition)
ISBN: B000ONFLUM
EAN: N/A
Year: 2004
Pages: 503

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