THE UNIVERSITY OF WESTERN AUSTRALIA
School of Computer Science & Software Engineering
CITS3211 CONCURRENT SYSTEMS PART II
Laboratory Sheet 2 (not assessed)
The purpose of this laboratory is to :
- Learn how to synchronize the execution of multiple threads in a Java
program.
- Learn about other methods and declarations related to concurrency
support in Java.
- Learn how deadlocks can arise in concurrent code, and what to do
to fix them.
Synchronizing Threads
Till now, our threads mostly have executed at their own will or, rather at the
will of the scheduler. However, as you have seen in the lectures, you may need
to coordinate the access of multiple threads to shared objects in your program.
Java allows you to coordinate the actions of multiple threads using
synchronized methods and synchronized statements. Here is how
it is done.
The synchronized methods are declared with the synchronized
keyword. Only one synchronized method can be invoked for an object at a
given point. This prevents synchronized methods from multiple objects from
conflicting with each other.
All classes and objects are associated with a unique monitor.
The monitor associated with an object is used to control the way in
which synchronized methods access the class or object. When a synchronized
method is invoked for an object, it is said to acquire
the monitor for that object.
No other synchronized method can be invoked for that object until the
monitor is released. A monitor is automatically released when the execution
of the method is complete or when the synchronized method executes a
method like wait(). In the second case, the thread associated
with the currently executing synchronized method becomes not runnable
until the wait condition is satisfied and no other method has acquired the
object's monitor.
Here is an example program for synchronizing
the execution of two threads. This is based on the Example1.java
program in lab1.
Here is some explanation of the program.
- The Example5 class is the same as the Example1
class.
- The MyThread class is modified to use the SynchronizedOutput
class. We use the method display in this class to display the
output instead of doing it in the run() method.
- It is important to note here that the display method is
static and applies to the SynchronizedOutput class as a whole and
not to an instance of this class.
- The main difference is the following. When display() is not
synchronized, it may be invoked by one thread which displays some output
and the other thread executes and so on. But now, thread1 invokes
display and acquires a monitor for the SynchronizedOutput
class and display() proceeds with the output of thread1. thread2
must wait until the monitor is released.
Java Concurrency Constructs
The following are the concurrency constructs and support in the Java
language. Though this list is quite small, it is possible to write
a wide range of concurrent programs with these constructs.
- The class java.lang.Thread used to initiate and control
new threads. You have already seen some examples.
- The keywords synchronized and volatile. You have
seen the use of the former in the example above. The volatile
keyword is used to declare variables which may be updated
asynchronously by multiple threads. Since we don't know the
order in which such variables will be modified, the volatile
keyword informs the compiler of the special nature of such a variable.
- The methods wait, notify and notifyAll
defined in java.lang.Object. These methods are used to
coordinate activities across threads.
- See the section on Synchronizing Threads in the Java Tutorial for more details.
Waiting and Notification
- The wait, notify and notifyAll methods are
used in programs in which activities in different threads are intended
to affect one another.
- Any synchronized method in any object can contain a wait which
suspends the current thread. As you have learnt earlier, a thread
can execute a synchronized method by acquiring the
monitor for the object. When a thread executes
wait on an object, it releases the monitor for the object (and any other
monitors it holds) and other threads will be able to acquire the monitor.
- All threads waiting on the target object (the monitor), are resumed
upon invocation of the notifyAll method. One arbitrarily chosen
thread is resumed upon invocation of the notify method.
- Here is an example program
Example6.java using wait
and notifyAll for synchronizing threads.
- In Java, both sleep and wait can be broken abnormally by
an InterruptedException and hence a wait call should
be enclosed in a try block.
- If several threads are trying to acquire a monitor object to
execute synchronized methods, the monitor puts all such
threads in a queue. A thread in the queue waits for the synchronized
method(s) to be free. A thread may also get queued if it has explicitly
called wait. However, such a thread can only wake up if it
is notified by a notify or notifyAll.
- Note: It is an error for a thread executing outside a monitor
(i.e., a synchronized method) to issue a wait, notify
or notifyAll. This causes an
IllegalMonitorStateException to be thrown.
Tasks
Synchronization and Deadlock
[AN ADDITIONAL EXERCISE ON SYNCHRONIZATION AND DEADLOCKS APPEARS BELOW.]
The file Account.java contains a simple
multithreaded program involving two bank accounts, with a thread for
each account that repeatedly transfers money to the other account.
This code is unsynchronized, and it also allows bank balances to go
negative.
Tasks
Take a copy of the Account.java file, and add appropriate
synchronization. Also, a thread performing a transfer should wait
if the account balance is less than the transfer amount.
(Hint: call notifyAll in an appropriate place to wake a waiting thread.)
Try running your program. What happens? Add appropriate print
statements to figure out exactly how far your program continues to
run. Most likely, your program will deadlock at a certain point
unless you have been very careful to avoid deadlocks.
Consider how you could fix your program to avoid deadlocks. There are
a number of different ways, although they all require some
reorganization of the code. (Hint: one of the easier approaches is to arrange
things so that both threads always acquire locks in the same order.)
You may find it useful to use a synchronized statement like
the following.
public void method() {
// Some code ...
synchronized (object) {
// Code run while holding the lock for "object"
}
// Some more code...
}
This allows you take any statement in your program and specify that it
must hold the lock on a particular object while it is running. See
the
Java Tutorial for more details.
April, 2008.