Thread Synchronization in JAVA
Threads share the same memory space, that
is, they can share resources. However,
there are critical situations
where it is desirable that only one thread at a time has
access to a
shared resource. For example, crediting and debiting a shared bank
account concurrently amongst several users without proper discipline,
will
jeopardize the integrity of the account data. Java provides
high-level concepts for
synchronization in order to control access to
shared resources.
A lock is used to synchronize access to a
shared resource. A lock can be associated
with a shared resource.
Threads gain access to a shared resource by first acquiring
the lock
associated with the resource. At any given time, at the most one thread
can
hold the lock (i.e., own the monitor) and thereby have access to the
shared resource.
A lock thus implements mutual exclusion.
In Java, all objects have a
lock—including arrays. This means that the lock from any
Java object can
be used to implement mutual exclusion. By associating a shared
resource
with a Java object and its lock, the object can act as a guard,
ensuring
synchronized access to the resource. Only one thread at a time
can access the
shared resource guarded by the object lock.
The object lock mechanism enforces the following rules of synchronization:
A thread must acquire the object lock
associated with a shared resource, before it
can enter the shared
resource. The runtime system ensures that no other thread
can enter a
shared resource if another thread already holds the object lock
associated with the shared resource. If a thread cannot immediately
acquire the
object lock, it is blocked, that is, it must wait for the
lock to become available.
When a thread exits a shared resource,
the runtime system ensures that the object
lock is also relinquished. If
another thread is waiting for this object lock, it can
proceed to
acquire the lock in order to gain access to the shared resource.
Classes also have a class-specific lock
that is analogous to the object lock.
Such a lock is actually a lock on
the java.lang.Class object associated with
the class. Given a class A,
the reference A.class denotes this unique Class
object. The class lock
can be used in much the same way as an object lock
to implement mutual
exclusion.The keyword synchronized and the lock
form
the basis for implementing synchronized execution of code. There
are two
ways in which execution of code can be synchronized:
- synchronized methods
- synchronized blocks
Synchronized Methods
If the methods of an object should only
be executed by one thread at a time, then
the declaration of all such
methods should be specified with the keyword synchronized.
A thread
wishing to execute a synchronized method must first obtain the object's
lock,before it can enter the object to execute the method. This is
simply achieved
by calling the method. If the lock is already held by
another thread, the calling
thread waits. No particular action on the
part of the program is necessary.
A thread relinquishes the lock simply
by returning from the synchronized method,
allowing the next thread
waiting for this lock to proceed.
Synchronized methods are useful in
situations where methods can manipulate the
state of an object in ways
that can corrupt the state if executed concurrently. A
stack
implementation usually defines the two operations push and pop as
synchronized, so that pushing and popping of elements are mutually
exclusive
operations. If several threads were to share a stack, then one
thread would, for
example, not be able to push an element on the stack
while another thread was
popping the stack. The integrity of the stack
is maintained in the face of several
threads accessing the state of the
same stack.
Synchronized Blocks
Whereas execution of synchronized
methods of an object is synchronized on the
lock of the object, the
synchronized block allows execution of arbitrary code to
be synchronized
on the lock of an arbitrary object. The general form of the
synchronized statement is as follows:
synchronized (<object reference expression>) { <code block> }
The <object reference expression>
must evaluate to a non-null reference value,
otherwise, a
NullPointerException is thrown. The code block is usually related to
the
object on which the synchronization is being done. This is the case
with
synchronized methods, where the execution of the method is
synchronized on the
lock of the current object:
public Object pop() {
synchronized (this) {
// Synchronized block on current object // ...
}
}
Once a thread has entered the code block
after acquiring the lock on the specified
object, no other thread will
be able to execute the code block, or any other code
aquiring the same
object lock, until the lock is relinquished. This happens when
the
execution of the code block completes normally or an uncaught exception
is
thrown. In contrast to synchronized methods, this mechanism allows
fine-grained
synchronization of code on arbitrary objects.
Object specification in the synchronized
statement is mandatory. A class can
choose to synchronize the execution
of a part of a method, by using the this
reference and putting the
relevant part of the method in the synchronized block.
The braces of the
block cannot be left out, even if the code block has just one
statement.
class SmartClient {
BankAccount account;
public void updateTransaction() {
synchronized (account) {
synchronized block account.update();
}
}
}
Synchronized blocks can also be specified on a class lock:
synchronized (<class name>.class) { <code block> }
The block synchronizes on the lock of
the object denoted by the reference
<class name>.class. A static
synchronized method classAction() in class A
is equivalent to the
following declaration:
static void classAction() {
synchronized (A.class) { }
}
In summary, a thread can hold a lock on an object
- by executing a synchronized instance method of the object
- by executing the body of a synchronized block that synchronizes on the object
- by executing a synchronized static method of a class
Thread Priorities
Every Java thread has a priority that helps the operating system determine the
order in which threads are scheduled.Java thread priorities are in the range
between MIN_PRIORITY (a constant of 1) and MAX_PRIORITY (a constant of
10).
By default, every thread is given priority NORM_PRIORITY (a
constant of 5).
Threads with higher priority are more
important to a program and should be
allocated processor time before
lower-priority threads. However, thread priorities
cannot guarantee the
order in which threads execute and very much platform
dependentant.
Thread Scheduler
Schedulers in JVM implementations usually employ one of the two following strategies:
Preemptive scheduling.If a thread with a higher priority than
the current running
thread moves to the Ready-to-run state, then the
current running thread can be
preempted (moved to the Ready-to-run
state) to let the higher priority thread execute.
Time-Sliced or Round-Robin scheduling.
A running thread is allowed to execute
for a fixed length of time, after which it
moves to the Ready-to-run
state to await its turn to run again.
It should be pointed out that thread
schedulers are implementation- and
platform-dependent; therefore, how
threads will be scheduled is unpredictable, at
least from platform to
platform.
Waiting and Notifying
Waiting and notifying provide means of
communication between threads that
synchronize on the same object.
The
threads execute wait() and notify() (or notifyAll()) methods on the
shared object
for this purpose. These final methods are defined in the
Object class, and therefore,
inherited by all objects.These methods can only be executed on an
object whose
lock the thread holds, otherwise, the call will result in
an IllegalMonitorStateException.
final void wait(long timeout) throws
InterruptedException final void wait(long
timeout, int nanos) throws
InterruptedException
final void wait() throws InterruptedException. A thread invokes the wait() method
on the object whose lock it holds. The thread is added to the wait set of the object.
final void notify() final void notifyAll() A thread invokes a notification method
on
the object whose lock it holds to notify thread(s) that are in the
wait set of the
object. Communication between threads is facilitated by waiting and notifying,
A thread usually calls the wait()
method on the object whose lock it holds because
a condition for its
continued execution was not met. The thread leaves the Running
state and
transits to the Waiting-for-notification state. There it waits for this
condition
to occur. The thread relinquishes ownership of the object
lock.
Transition to the
Waiting-for-notification state and relinquishing the object lock
are
completed as one atomic (non-interruptable) operation. The releasing of
the
lock of the shared object by the thread allows other threads to run
and execute
synchronized code on the same object after acquiring its
lock.
Note that the waiting thread does not
relinquish any other object locks that it
might hold, only that of the
object on which the wait() method was invoked.
Objects that have these
other locks remain locked while the thread is waiting.
Each object has a wait set containing
threads waiting for notification. Threads
in the
Waiting-for-notification state are grouped according to the object
whose
wait() method they invoked.A thread in the Waiting-for-notification state
can be awakened by the occurrence of any one of these three incidents:
- The waiting thread times out.
- Another thread interrupts the waiting thread.
- Another thread invokes the notify()
method on the object of the waiting and the waiting thread is
selected as the thread to be awakened.
Notify
Invoking the notify() method on an object wakes up a single thread that is waiting
on the lock of this object. The selection of a thread to awaken is dependent on the
thread policies implemented by the JVM. On being notified, a waiting thread first
transits to the Blocked-for-lock-acquisition state to acquire the lock on the object,
and not directly to the Ready-to-run state. The thread is also removed from the
wait set of the object. Note that the object lock is not relinquished when the
notifying thread invokes the notify() method. The notifying thread relinquishes the
lock at its own discretion, and the awakened thread will not be able to run until
the notifying thread relinquishes the object lock.
When the notified thread obtains the object lock, it is enabled for execution,
waiting in the Ready-to-run state for its turn to execute again. Finally, when it does
get to execute, the call to the wait() method returns and the thread can continue
with its execution.
A call to the notify() method has no consequences if there are no threads in the
wait set of the object.
In contrast to the notify() method, the notifyAll() method wakes up all threads in
the wait set of the shared object. They will all transit to the
Blocked-for-lock-acquisition state and contend for the object lock as explained
earlier.
Joining
A thread can invoke the overloaded method join() on another thread in order to
wait for the other thread to complete its execution before continuing, that is, the
first thread waits for the second thread to join it after completion. A running
thread t1 invokes the method join() on a thread t2. The join() call has no effect if
thread t2 has already completed. If thread t2 is still alive, then thread t1 transits
to the Blocked-for-join-completion state. Thread t1 waits in this state until one of
these events occur-
Thread t2 completes-In this case thread t1 is enabled and when it gets to run, it
will continue normally after the join() method call.
Thread t1 is timed out.-The time specified in the argument in the join() method
call has elapsed, without thread t2 completing. In this case as well, thread t1 is
enabled. When it gets to run, it will continue normally after the join() method call.
Thread t1 is interrupted-Some thread interrupted thread t1 while thread t1 was
waiting for join completion. Thread t1 is enabled, but when it gets to execute, it
will now throw an InterruptedException.
Deadlocks
A deadlock is a situation where a thread is waiting for an object lock that another
thread holds, and this second thread is waiting for an object lock that the first
thread holds. Since each thread is waiting for the other thread to relinquish a
lock, they both remain waiting forever in the Blocked-for-lock-acquisition state.
The threads are said to be deadlocked.
Thread t1 has a lock on object o1, but cannot acquire the lock on object o2.
Thread t2 has a lock on object o2, but cannot acquire the lock on object o1.
They can only proceed if one of them relinquishes a lock the other one wants,
which is never going to happen.
public class DeadlockExample {
public static void main(String[] args) {
final String resource1 = "ratan jaiswal";
final String resource2 = "vimal jaiswal";
// t1 tries to lock resource1 then resource2
Thread t1 = new Thread() {
public void run() {
synchronized (resource1) {
System.out.println("Thread 1: locked resource 1");
try { Thread.sleep(100);} catch (Exception e) {}
synchronized (resource2) {
System.out.println("Thread 1: locked resource 2");
}
}
}
};
// t2 tries to lock resource2 then resource1
Thread t2 = new Thread() {
public void run() {
synchronized (resource2) {
System.out.println("Thread 2: locked resource 2");
try { Thread.sleep(100);} catch (Exception e) {}
synchronized (resource1) {
System.out.println("Thread 2: locked resource 1");
}
}
} ;
t1.start();
t2.start();
}//end of main
}// end of class
}