This is part 5 of this series. If you missed any of the last four, you can find them here:
- Part 1 – Overview
- Part 2 – Lifecycle of threads
- Part 3 – Daemon threads
- Part 4 – Interrupting threads
In this post, we’ll look at thread synchronization and the monitor model.
Thread Synchronization
In most practical multithreaded applications, two or more threads may need to share access to the same objects. If two threads have access to the same object, and each calls a method that modifies the state of the object, the data stored in the object may become corrupted. This is commonly known as a race condition. It is therefore important to ensure exclusive access to methods that update or modify data. For example, if the deposit()
and withdraw()
methods on a BankAccount
object are called simultaneously, the balance may not be correctly calculated.
To avoid simultaneous access of a shared object by multiple threads, access must be synchronized. A synchronized method can only be executed by one thread at a time. While one thread is executing a synchronized method, any other thread that wants to execute either the same method or any other synchronized method on the same object will be forced to wait until the first thread has finished executing the synchronized block.
Object Locks
Every Java object has an invisible one-bit field called a monitor or object lock. This monitor controls access to the object: it is set (becomes unavailable) when a synchronized method is executing. Once it is set, other synchronized methods of the object must wait until the bit has been reset (becomes available) before they can execute.
A thread will release the object lock when it has finished executing the synchronized method, or when it exits the method by throwing an exception.
If a thread owns the lock of an object and it calls another synchronized method of the same object, then that thread is automatically granted access to the method. The thread only relinquishes the lock when it exits the last synchronized method.
It is important to understand that it is the object that is locked, and not the method itself. Multiple threads can access different instances of the same class without synchronization issues. They can even invoke the same synchronized method at the same time, provided the method is invoked on different objects.
The class itself also has a lock: this is used for synchronized static methods.
The synchronized Keyword
A method is synchronized by using the synchronized
keyword. For example:
public class BankAccount {
// a primitive data type here
private double balance;
public synchronized void deposit(double amount) {
balance += amount;
}
} // end of class
The synchronized
keyword can be applied to a method as we’ve just seen, or to a code block. Applying it to a code block allows finer granularity of control and faster processing because fewer statements are locked. Deciding which statements can be outside the block requires a lot of careful thought, though.
public void deposit(double amount) {
statements; // not synchronized
synchronized (this) {
balance += amount; // synchronized statements
}
statements; // not synchronized
}
The this
keyword in this context specifies that we are synchronizing access on the current object’s monitor. We can also use the monitor of any other object (most probably an instance field of the first object):
public class BankAccount {
// an object reference now instead of a primitive type
private Double balance;
public void deposit(Double amount) {
statements; // not synchronized
synchronized (balance) {
balance += amount; // synchronized statements
}
statements; // not synchronized
}
} // end of class
Other threads are free to call any unsynchronized methods on a locked object. For example, we could have a getAccountNumber()
method in the BankAccount
class that could access the account number. This method would not need to be synchronized because it would not change the balance
field. So getAccountNumber()
could therefore be run even if another thread was running the synchronized deposit()
method.
Synchronized Methods
There are several other methods that we use when working with an object’s monitor. These methods are in the Object
class:
-
Once a thread has entered a synchronized method or code block, it may not be able to proceed for some reason. The thread can then call the
wait()
method and enter a wait list for that object. The scheduler ignores the thread until it’s removed from the wait list. This is done when the thread is notified of a change in the object’s status. -
The
wait()
method can take along
argument which specifies the maximum length of time that the thread will wait. Ifwait()
is called without a parameter, the thread will wait indefinitely. -
When a thread calls
wait()
within a synchronized method, it releases the lock. This allows another thread to enter a synchronized code block on the same object. This thread might reset the condition that caused the first thread to enter the wait state. Once the condition is corrected, this subsequent thread should call thenotify()
ornotifyAll()
method before leaving the synchronized block. -
To remove a thread from the wait list, some other thread must call the
notify()
ornotifyAll()
method on the same object.- The
notify()
method removes a single arbitrarily selected thread from the wait list. - The
notifyAll()
method will remove all threads from that object’s wait list.
- The
Consider the following simplified example using wait()
and notify()
:
public class BankAccount {
// a primitive data type here
private double balance;
public synchronized double withdraw (double amount) {
while (balance < amount) {
wait();
}
balance -= amount;
return amount;
}
public synchronized void deposit(double amount) {
balance += amount;
notifyAll();
}
} // end of class
Obviously this example is contrived because we definitely wouldn’t want a withdrawal from a bank account to block indefinitely while we waited for a deposit.
The Java API documentation contains a lot more information on how to use the wait()
, notify()
and notifyAll()
methods.
Conclusion
In the next post we’ll start looking at the new concurrency classes in the java.util.concurrent
package.
I look forward to reading your comments.