Baixe Capítulo 32 - Programación Multiproceso y Paralelo - Liang-Java-Comp11e e outras Resumos em PDF para Informática, somente na Docsity!
Objectives
■ ■ To get an overview of multithreading (§32.2).
■ ■ To develop task classes by implementing the Runnable interface
■ ■ To create threads to run tasks using the Thread class (§32.3).
■ ■ To control threads using the methods in the Thread class (§32.4).
■ ■ To control animations using threads and use Platform.runLater to
run the code in the application thread (§32.5).
■ ■ To execute tasks in a thread pool (§32.6).
■ ■ To use synchronized methods or blocks to synchronize threads to avoid
race conditions (§32.7).
■ ■ To synchronize threads using locks (§32.8).
■ ■ To facilitate thread communications using conditions on locks (§§32.
and 32.10).
■ ■ To use blocking queues ( ArrayBlockingQueue , LinkedBlocking-
Queue , and PriorityBlockingQueue ) to synchronize access to a
queue (§32.11).
■ ■ To restrict the number of concurrent accesses to a shared resource using
semaphores (§32.12).
■ ■ To use the resource-ordering technique to avoid deadlocks (§32.13).
■ ■ To describe the life cycle of a thread (§32.14).
■ ■ To create synchronized collections using the static methods in the
Collections class (§32.15).
■ ■ To develop parallel programs using the Fork/Join Framework (§32.16).
Multithreading
and Parallel
Programming
CHAPTER
32-2 Chapter 32 Multithreading and Parallel Programming
32.1 Introduction
Multithreading enables multiple tasks in a program to be executed concurrently.
One of the powerful features of Java is its built-in support for multithreading— the concurrent
running of multiple tasks within a program. In many programming languages, you have to
invoke system-dependent procedures and functions to implement multithreading. This chapter
introduces the concepts of threads and how multithreading programs can be developed in Java.
32.2 Thread Concepts
A program may consist of many tasks that can run concurrently. A thread is the flow
of execution, from beginning to end, of a task.
A thread provides the mechanism for running a task. With Java, you can launch multiple
threads from a program concurrently. These threads can be executed simultaneously in multi-
processor systems, as shown in Figure 32.1a.
Point multithreading^ Key
Point
Key
thread task
Figure 32.1 (a) Multiple threads running on multiple CPUs. (b) Multiple threads share a
single CPU.
Thread 1
Thread 3
Thread 2
(a)
Thread 1
Thread 3
Thread 2
(b)
In single-processor systems, as shown in Figure 32.1b, the multiple threads share CPU time,
known as time sharing , and the operating system is responsible for scheduling and allocating
resources to them. This arrangement is practical because most of the time the CPU is idle. It
does nothing, for example, while waiting for the user to enter data.
Multithreading can make your program more responsive and interactive as well as enhance
performance. For example, a good word processor lets you print or save a file while you are
typing. In some cases, multithreaded programs run faster than single-threaded programs even
on single-processor systems. Java provides exceptionally good support for creating and running
threads, and for locking resources to prevent conflicts.
You can create additional threads to run concurrent tasks in the program. In Java, each task
is an instance of the Runnable interface, also called a runnable object. A thread is essentially
an object that facilitates the execution of a task.
32.2.1 Why is multithreading needed? How can multiple threads run simultaneously in a
single-processor system?
32.2.2 What is a runnable object? What is a thread?
32.3 Creating Tasks and Threads
A task class must implement the Runnable interface. A task must be run from a
thread.
Tasks are objects. To create tasks, you have to first define a class for tasks, which implements
the Runnable interface. The Runnable interface is rather simple. All it contains is the run()
method. You need to implement this method to tell the system how your thread is going to run.
A template for developing a task class is shown in Figure 32.2a.
time sharing
task runnable object thread
Point
Check
Point
Key
Runnable interface run() method
32-4 Chapter 32 Multithreading and Parallel Programming
Listing 32.1 TaskThreadDemo.java
1 public class TaskThreadDemo { 2 public static void main(String[] args) { 3 // Create tasks 4 Runnable printA = new PrintChar( 'a' , 100 ); 5 Runnable printB = new PrintChar( 'b' , 100 ); 6 Runnable print100 = new PrintNum( 100 ); 7 8 // Create threads 9 Thread thread1 = new Thread(printA); 10 Thread thread2 = new Thread(printB); 11 Thread thread3 = new Thread(print100); 12 13 // Start threads 14 thread1.start(); 15 thread2.start(); 16 thread3.start(); 17 } 18 } 19 20 // The task for printing a character a specified number of times 21 class PrintChar implements Runnable { 22 private char charToPrint; // The character to print 23 private int times; // The number of times to repeat 24 25 /** Construct a task with a specified character and number of 26 * times to print the character 27 / 28 public PrintChar( char c, int t) { 29 charToPrint = c; 30 times = t; 31 } 32 33 @Override /* Override the run() method to tell the system 34 * what task to perform 35 / 36 public void run() { 37 for ( int i = 0 ; i < times; i++) { 38 System.out.print(charToPrint); 39 } 40 } 41 } 42 43 // The task class for printing numbers from 1 to n for a given n 44 class PrintNum implements Runnable { 45 private int lastNum; 46 47 /* Construct a task for printing 1, 2, ..., n / 48 public PrintNum( int n) { 49 lastNum = n; 50 } 51 52 @Override /* Tell the thread how to run */ 53 public void run() { 54 for ( int i = 1 ; i <= lastNum; i++) { 55 System.out.print( " " + i); 56 } 57 } 58 }
create tasks
create threads
start threads
task class
run
task class
run
32.3 Creating Tasks and Threads 32-
The program creates three tasks (lines 4–6). To run them concurrently, three threads are created
(lines 9–11). The start() method (lines 14–16) is invoked to start a thread that causes the
run() method in the task to be executed. When the run() method completes, the thread
terminates.
Because the first two tasks, printA and printB , have similar functionality, they can
be defined in one task class PrintChar (lines 21–41). The PrintChar class implements
Runnable and overrides the run() method (lines 36–40) with the print-character action. This
class provides a framework for printing any single character a given number of times. The
runnable objects, printA and printB , are instances of the PrintChar class.
The PrintNum class (lines 44–58) implements Runnable and overrides the run() method
(lines 53–57) with the print-number action. This class provides a framework for printing num-
bers from 1 to n , for any integer n. The runnable object print100 is an instance of the class
printNum class.
Note If you don’t see the effect of these three threads running concurrently, increase the number of characters to be printed. For example, change line 4 to Runnable printA = new PrintChar( 'a' , 10000);
Important Note The run() method in a task specifies how to perform the task. This method is automati- cally invoked by the JVM. You should not invoke it. Invoking run() directly merely executes this method in the same thread; no new thread is started.
32.3.1 How do you define a task class? How do you create a thread for a task?
32.3.2 What would happen if you replace the start() method with the run() method in
lines 14–16 in Listing 32.1?
effect of concurrency
run() method
Point
Check
print100.start(); printA.start(); printB.start();
Replaced by print100.run(); printA.run(); printB.run();
32.3.3 What is wrong in the following two programs? Correct the errors.
public class Test implements Runnable { public static void main(String[] args) { new Test(); }
public Test() { Test task = new Test(); new Thread(task).start(); }
public void run() { System.out.println( "test" ); } }
public class Test implements Runnable { public static void main(String[] args) { new Test(); }
public Test() { Thread t = new Thread( this ); t.start(); t.start(); }
public void run() { System.out.println( "test" ); } } (a) (b)
32.4 The Thread Class 32-
This approach is, however, not recommended because it mixes the task and the mechanism of running the task. Separating the task from the thread is a preferred design.
Note The Thread class also contains the stop() , suspend() , and resume() methods. As of Java 2, these methods were deprecated (or outdated ) because they are known to be inherently unsafe. Instead of using the stop() method, you should assign null to a Thread variable to indicate that it has stopped.
You can use the yield() method to temporarily release time for other threads. For example,
suppose that you modify the code in the run() method in lines 53–57 for PrintNum in
Listing 32.1 as follows:
public void run() { for ( int i = 1 ; i <= lastNum; i++) { System.out.print( " " + i); Thread.yield(); } }
Every time a number is printed, the thread of the print100 task is yielded to other threads.
The sleep(long millis) method puts the thread to sleep for a specified time in milli-
seconds to allow other threads to execute. For example, suppose that you modify the code in
lines 53–57 in Listing 32.1 as follows:
public void run() { try { for ( int i = 1 ; i <= lastNum; i++) { System.out.print( " " + i); if (i >= 50 ) Thread.sleep( 1 ); } } catch (InterruptedException ex) { } }
Every time a number ( >= 50 ) is printed, the thread of the print100 task is put to sleep for
1 millisecond.
The sleep method may throw an InterruptedException , which is a checked exception.
Such an exception may occur when a sleeping thread’s interrupt() method is called. The
interrupt() method is very rarely invoked on a thread, so an InterruptedException is
unlikely to occur. But since Java forces you to catch checked exceptions, you have to put it in
a try-catch block. If a sleep method is invoked in a loop, you should wrap the loop in a
try-catch block, as shown in (a) below. If the loop is outside the try-catch block, as
shown in (b), the thread may continue to execute even though it is being interrupted.
deprecated method
yield()
sleep(long)
InterruptedException
public void run() { try { while (...) { ... Thread.sleep( 1000 ); } } catch (InterruptedException ex) { ex.printStackTrace(); } }
public void run() { while (...) { try { ... Thread.sleep(sleepTime); } catch (InterruptedException ex) { ex.printStackTrace(); } } } (a) Correct (b) Incorrect
32-8 Chapter 32 Multithreading and Parallel Programming
You can use the join() method to force one thread to wait for another thread to finish. For
example, suppose that you modify the code in lines 53–57 in Listing 32.1 as follows:
join()
Thread print
Wait for thread to finish
Thread thread
thread4 finished
public void run() { Thread thread4 = new Thread( new PrintChar( 'c', 40 )); thread4.start(); try { for ( int i = 1 ; i <= lastNum; i++) { System.out.print ( " " + i); if (i == 50 ) thread4.join(); } } catch (InterruptedException ex) { } }
thread4.join()
A new thread4 is created and it prints character c 40 times. The numbers from 50 to 100
are printed after thread thread4 is finished.
Java assigns every thread a priority. By default, a thread inherits the priority of the thread
that spawned it. You can increase or decrease the priority of any thread by using the
setPriority method and you can get the thread’s priority by using the getPriority
method. Priorities are numbers ranging from 1 to 10. The Thread class has the int constants
MIN_PRIORITY , NORM_PRIORITY , and MAX_PRIORITY , representing 1 , 5 , and 10 , respec-
tively. The priority of the main thread is Thread.NORM_PRIORITY.
The JVM always picks the currently runnable thread with the highest priority. A lower
priority thread can run only when no higher priority threads are running. If all runnable threads
have equal priorities, each is assigned an equal portion of the CPU time in a circular queue.
This is called round-robin scheduling. For example, suppose that you insert the following code
in line 16 in Listing 32.1:
thread3.setPriority(Thread.MAX_PRIORITY);
The thread for the print100 task will be finished first.
Tip The priority numbers may be changed in a future version of Java. To minimize the impact of any changes, use the constants in the Thread class to specify thread priorities.
Tip A thread may never get a chance to run if there is always a higher priority thread running or a same-priority thread that never yields. This situation is known as contention or starvation. To avoid contention, the thread with higher priority must periodically invoke the sleep or yield method to give a thread with a lower or the same priority a chance to run.
32.4.1 Which of the following methods are instance methods in java.lang.Thread?
Which method may throw an InterruptedException? Which of them are
deprecated in Java?
run , start , stop , suspend , resume , sleep , interrupt , yield , join
32.4.2 If a loop contains a method that throws an InterruptedException , why should
the loop be placed inside a try-catch block?
32.4.3 How do you set a priority for a thread? What is the default priority?
setPriority(int)
round-robin scheduling
contention or starvation
Point
Check
32-10 Chapter 32 Multithreading and Parallel Programming
42 // Create a scene and place it in the stage 43 Scene scene = new Scene(pane, 200 , 50 ); 44 primaryStage.setTitle( "FlashText" ); // Set the stage title 45 primaryStage.setScene(scene); // Place the scene in the stage 46 primaryStage.show(); // Display the stage 47 } 48 }
The program creates a Runnable object in an anonymous inner class (lines 17–40). This object
is started in line 40 and runs continuously to change the text in the label. It sets a text in the
label if the label is blank (line 23) and sets its text blank (line 25) if the label has a text. The
text is set and unset to simulate a flashing effect.
JavaFX GUI is run from the JavaFX application thread. The flashing control is run from a
separate thread. The code in a nonapplication thread cannot update GUI in the application
thread. To update the text in the label, a new Runnable object is created in lines 27–32. Invok-
ing Platform.runLater(Runnable r) tells the system to run this Runnable object in the
application thread.
The anonymous inner classes in this program can be simplifed using lambda expressions
as follows:
new Thread(() -> { // lambda expression try { while ( true ) { if (lblText.getText().trim().length() == 0 ) text = "Welcome" ; else text = "" ; Platform.runLater(() -> lblText.setText(text)); // lambda exp
Thread.sleep( 200 ); } } catch (InterruptedException ex) { } }).start();
32.5.1 What causes the text to flash? 32.5.2 Is an instance of FlashText a runnable object? 32.5.3 What is the purpose of using Platform.runLater? 32.5.4 Can you replace the code in lines 27–32 using the following code?
Platform.runLater(e -> lblText.setText(text));
32.5.5 What happens if line 34 ( Thread.sleep(200) ) is not used? 32.5.6 There is an issue in Listing 16.9, ListViewDemo. If you press the CTRL key and
select Canada, Demark, and China in this order, you will get an ArrayIndex-
OutBoundsException. What is the reason for this error and how do you fix it?
(Thanks to Henri Heimonen of Finland for contributing this question).
32.6 Thread Pools
A thread pool can be used to execute tasks efficiently.
In Section 32.3, Creating Tasks and Threads, you learned how to define a task class by imple-
menting java.lang.Runnable , and how to create a thread to run a task like this:
Runnable task = new TaskClass(...); new Thread(task).start();
JavaFX application thread
Platform.runLater
Point
Check
Point
Key
32.6 Thread Pools 32-
To create an Executor object, use the static methods in the Executors class, as shown
in Figure 32.8. The newFixedThreadPool(int) method creates a fixed number of threads
in a pool. If a thread completes executing a task, it can be reused to execute another task. If
a thread terminates due to a failure prior to shutdown, a new thread will be created to replace
it if all the threads in the pool are not idle and there are tasks waiting for execution. The
newCachedThreadPool() method creates a new thread if all the threads in the pool are not
idle and there are tasks waiting for execution. A thread in a cached pool will be terminated if
it has not been used for 60 seconds. A cached pool is efficient for many short tasks.
Figure 32.7 The Executor interface executes threads and the ExecutorService subinterface manages threads.
«interface» java.util.concurrent.ExecutorService
- execute(Runnable object): void
«interface» java.util.concurrent.Executor
Executes the runnable task.
Shuts down the executor, but allows the tasks in the executor to complete. Once shut down, it cannot accept new tasks. Shuts down the executor immediately even though there are unfinished threads in the pool. Returns a list of unfinished tasks. Returns true if the executor has been shut down. Returns true if all tasks in the pool are terminated.
This approach is convenient for a single task execution, but it is not efficient for a large
number of tasks because you have to create a thread for each task. Starting a new thread for
each task could limit throughput and cause poor performance. Using a thread pool is an ideal
way to manage the number of tasks executing concurrently. Java provides the Executor
interface for executing tasks in a thread pool and the ExecutorService interface for man-
aging and controlling tasks. ExecutorService is a subinterface of Executor , as shown
in Figure 32.7.
Figure 32.8 The Executors class provides static methods for creating Executor objects.
Creates a thread pool with a fixed number of threads executing concurrently. A thread may be reused to execute another task after its current task is finished. Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.
java.util.concurrent.Executors
+newFixedThreadPool(numberOfThreads: int): ExecutorService
+newCachedThreadPool(): ExecutorService
Listing 32.3 shows how to rewrite Listing 32.1 using a thread pool.
Listing 32.3 ExecutorDemo.java
1 import java.util.concurrent.*; 2 3 public class ExecutorDemo {
32.7 Thread Synchronization 32-
Figure 32.9 AccountWithoutSync contains an instance of Account and 100 threads of AddAPennyTask.
100 1 AddAPennyTask
+run(): void
AccountWithoutSync
-account: Account
+main(args: String[]): void
1 1 Account
+getBalance(): int +deposit(amount: int): void
-balance: int
«interface» java.lang.Runnable
Listing 32.4 AccountWithoutSync.java
1 import java.util.concurrent.*; 2 3 public class AccountWithoutSync { 4 private static Account account = new Account(); 5 6 public static void main(String[] args) { 7 ExecutorService executor = Executors.newCachedThreadPool(); 8 9 // Create and launch 100 threads 10 for ( int i = 0 ; i < 100 ; i++) { 11 executor.execute( new AddAPennyTask()); 12 } 13 14 executor.shutdown(); 15 16 // Wait until all tasks are finished 17 while (!executor.isTerminated()) { 18 } 19 20 System.out.println( "What is balance? " + account.getBalance()); 21 } 22 23 // A thread for adding a penny to the account 24 private static class AddAPennyTask implements Runnable { 25 public void run() { 26 account.deposit( 1 ); 27 } 28 } 29 30 // An inner class for account 31 private static class Account { 32 private int balance = 0 ; 33 34 public int getBalance() { 35 return balance; 36 } 37 38 public void deposit( int amount) { 39 int newBalance = balance + amount; 40 41 // This delay is deliberately added to magnify the
create executor
submit task
shut down executor
wait for all tasks to terminate
32-14 Chapter 32 Multithreading and Parallel Programming
42 // data-corruption problem and make it easy to see. 43 try { 44 Thread.sleep( 5 ); 45 } 46 catch (InterruptedException ex) { 47 } 48 49 balance = newBalance; 50 } 51 } 52 }
The classes AddAPennyTask and Account in lines 24–51 are inner classes. Line 4 creates
an Account with initial balance 0. Line 11 creates a task to add a penny to the account and
submits the task to the executor. Line 11 is repeated 100 times in lines 10–12. The program
repeatedly checks whether all tasks are completed in lines 17 and 18. The account balance is
displayed in line 20 after all tasks are completed.
The program creates 100 threads executed in a thread pool executor (lines 10–12). The
isTerminated() method (line 17) is used to test whether all the threads in the pool are
terminated.
The balance of the account is initially 0 (line 32). When all the threads are finished, the
balance should be 100 but the output is unpredictable. As can be seen in Figure 32.10, the
answers are wrong in the sample run. This demonstrates the data-corruption problem that
occurs when all the threads have access to the same data source simultaneously.
Figure 32.10 The AccountWithoutSync program causes data inconsistency.
Lines 39–49 could be replaced by one statement:
balance = balance + amount;
It is highly unlikely, although plausible, that the problem can be replicated using this single
statement. The statements in lines 39–49 are deliberately designed to magnify the data-
corruption problem and make it easy to see. If you run the program several times but still
do not see the problem, increase the sleep time in line 44. This will increase the chances for
showing the problem of data inconsistency.
What, then, caused the error in this program? A possible scenario is shown in Figure 32.11.
Figure 32.11 Task 1 and Task 2 both add 1 to the same balance.
Step Balance Task 1 Task 2
1 0 newBalance = balance + 1; (^2 0) newBalance = balance + 1; 3 1 balance = newBalance; (^4 1) balance = newBalance;
32-16 Chapter 32 Multithreading and Parallel Programming
of the code in a method. This block is referred to as a synchronized block. The general form
of a synchronized statement is as follows:
synchronized (expr) { statements; }
The expression expr must evaluate to an object reference. If the object is already locked by
another thread, the thread is blocked until the lock is released. When a lock is obtained on the
object, the statements in the synchronized block are executed and then the lock is released.
Synchronized statements enable you to synchronize part of the code in a method instead
of the entire method. This increases concurrency. You can make Listing 32.4 thread-safe by
placing the statement in line 26 inside a synchronized block:
synchronized (account) { account.deposit( 1 ); }
Note Any synchronized instance method can be converted into a synchronized statement. For example, the following synchronized instance method in (a) is equivalent to (b):
synchronized block
public synchronized void xMethod() { // method body }
public void xMethod() { synchronized ( this ) { // method body } } (a) (b)
32.7.1 Give some examples of possible resource corruption when running multiple
threads. How do you synchronize conflicting threads?
32.7.2 Suppose you place the statement in line 26 of Listing 32.4 inside a synchronized
block to avoid race conditions, as follows:
synchronized ( this ) { account.deposit( 1 ); }
Will it work?
32.8 Synchronization Using Locks
Locks and conditions can be explicitly used to synchronize threads.
Recall that in Listing 32.4, 100 tasks deposit a penny to the same account concurrently, which
causes conflicts. To avoid it, you use the synchronized keyword in the deposit method,
as follows:
public synchronized void deposit( double amount)
A synchronized instance method implicitly acquires a lock on the instance before it executes
the method.
Java enables you to acquire locks explicitly, which give you more control for coordinating
threads. A lock is an instance of the Lock interface, which defines the methods for acquiring and
releasing locks, as shown in Figure 32.13. A lock may also use the newCondition() method
to create any number of Condition objects, which can be used for thread communications.
Point
Check
Point
Key
lock
32.8 Synchronization Using Locks 32-
Figure 32.13 The ReentrantLock class implements the Lock interface to represent a lock.
«interface» java.util.concurrent.locks.Lock
Acquires the lock. Releases the lock. Returns a new Condition instance that is bound to this Lock instance.
java.util.concurrent.locks.ReentrantLock
- lock(): void
- unlock(): void
- newCondition(): Condition
+ReentrantLock() +ReentrantLock(fair: boolean)
Same as ReentrantLock(false). Creates a lock with the given fairness policy. When the fairness is true, the longest-waiting thread will get the lock. Otherwise, there is no particular access order.
ReentrantLock is a concrete implementation of Lock for creating mutually exclusive
locks. You can create a lock with the specified fairness policy. True fairness policies guarantee
that the longest waiting thread will obtain the lock first. False fairness policies grant a lock
to a waiting thread arbitrarily. Programs using fair locks accessed by many threads may have
poorer overall performance than those using the default setting, but they have smaller variances
in times to obtain locks and prevent starvation.
Listing 32.5 revises the program in Listing 32.7 to synchronize the account modification
using explicit locks.
Listing 32.5 AccountWithSyncUsingLock.java
1 import java.util.concurrent.; 2 import java.util.concurrent.locks.; 3 4 public class AccountWithSyncUsingLock { 5 private static Account account = new Account(); 6 7 public static void main(String[] args) { 8 ExecutorService executor = Executors.newCachedThreadPool(); 9 10 // Create and launch 100 threads 11 for ( int i = 0 ; i < 100 ; i++) { 12 executor.execute( new AddAPennyTask()); 13 } 14 15 executor.shutdown(); 16 17 // Wait until all tasks are finished 18 while (!executor.isTerminated()) { 19 } 20 21 System.out.println( "What is balance? " + account.getBalance()); 22 } 23 24 // A thread for adding a penny to the account 25 public static class AddAPennyTask implements Runnable { 26 public void run() { 27 account.deposit( 1 ); 28 } 29 } 30
fairness policy
package for locks
32.9 Cooperation among Threads 32-
Let us use an example to demonstrate thread communications. Suppose you create and
launch two tasks: one that deposits into 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. Whenever new funds are deposited into 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., 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 32.15.
thread cooperation example
Figure 32.14 The Condition interface defines the methods for performing
synchronization.
«interface» java.util.concurrent.Condition
- await(): void
- signal(): void
- signalAll(): Condition
Causes the current thread to wait until the condition is signaled. Wakes up one waiting thread. Wakes up all waiting threads.
Figure 32.15 The condition newDeposit is used for communications between the two
threads.
while (balance < withdrawAmount) newDeposit.await();
Withdraw Task
balance – = withdrawAmount
lock.unlock();
Deposit Task
lock.lock();
newDeposit.signalAll();
balance += depositAmount
lock.unlock();
lock.lock();
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 the initial balance is 0 and the amount to deposit and withdraw are randomly gen-
erated. Listing 32.6 gives the program. A sample run of the program is shown in Figure 32.16.
Figure 32.16 The withdraw task waits if there are not sufficient funds to withdraw.
32-20 Chapter 32 Multithreading and Parallel Programming
Listing 32.6 ThreadCooperation.java
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 public static class DepositTask implements Runnable { 18 @Override // Keep adding an amount to the account 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 public static class WithdrawTask implements Runnable { 33 @Override // Keep subtracting an amount from the account 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 46 // Create a condition 47 private static Condition newDeposit = lock.newCondition(); 48 49 private int balance = 0 ; 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) {
create a lock
create a condition
acquire the lock
create two threads