- Details
- Written by Nam Ha Minh
- Last Updated on 13 August 2019   |   Print Email
Welcome to part 3 of Java Synchronization Tutorial series! In
part 2, you know how to use
Lock and
Condition objects for synchronizing access to a method. This is basically how synchronized is designed and working in Java. And to make it easier for programmers, Java provides the
synchronized keyword that operates on the default lock of a class. This default lock is called
intrinsic lock which belongs to every Java object.The synchronized keyword can be used at method level or code block level. Let’s look at the first approach first.
1. Synchronized Methods
Consider the following class:
public class A {
public synchronized void update() {
// code needs to be serialized for access
}
}
Here, the
update() method is synchronized. It is equivalent to the following code that uses a lock object explicitly:
public class A {
public void update() {
this.intrinsicLock.lock();
try {
// code needs to be serialized for access
} finally {
this.intrinsicLock.unlock();
}
}
}
Here, the intrinsic lock belongs to an instance of the class. And the following code explains how to use condition with a synchronized method:
public class A {
public synchronized void update() {
if (!condition) {
this.wait();
}
// code needs to be serialized for access
this.notify();
// or:
this.notifyAll();
}
}
The methods
wait(),
notify() and
notifyAll() behaves in the same manner as the methods
await(),
signal() and
signalAll() of a
Lock object. These methods are provided by the Object class. So every object has its own intrinsic lock and intrinsic condition.Now, the
Bank class can be rewritten using the
synchronized keyword as follows:
/**
* Bank.java
* This class represents a bank that manages accounts and provides
* money transfer function.
* It demonstrates how to use the the synchronized keyword to serialize
* access to methods.
* @author www.codejava.net
*/
public class Bank {
public static final int MAX_ACCOUNT = 10;
public static final int MAX_AMOUNT = 10;
public static final int INITIAL_BALANCE = 100;
private Account[] accounts = new Account[MAX_ACCOUNT];
public Bank() {
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new Account(INITIAL_BALANCE);
}
}
public synchronized void transfer(int from, int to, int amount) {
try {
while (accounts[from].getBalance() < amount) {
wait();
}
accounts[from].withdraw(amount);
accounts[to].deposit(amount);
String message = "%s transfered %d from %s to %s. Total balance: %d\n";
String threadName = Thread.currentThread().getName();
System.out.printf(message, threadName, amount, from, to, getTotalBalance());
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized int getTotalBalance() {
int total = 0;
for (int i = 0; i < accounts.length; i++) {
total += accounts[i].getBalance();
}
return total;
}
}
You see, using the
synchronized keyword make the code more compact, right? But you wouldn’t understand how a synchronized method works without understanding about the explicit locking mechanism, would you?
Let recompile the
Bank class and then run the
TransactionTest program again, you will see that the program behaves the same way as the previous version which uses explicit locking mechanism. But you write much less code. It’s cool, isn’t it?The following are some noteworthy points with regards to synchronized instance methods (non-static ones):
- When a thread is entering a synchronized method, it tries to acquire the intrinsic lock associate with the current instance of the class. If the thread successfully owns the lock, other threads will block when attempting to execute any synchronized instance methods of the class. That means if a class contains multiple synchronized instance methods, only one can be executed by a thread at a time.
- A thread must own the lock before calling wait(), notify() or notifyAll(). Otherwise an IllegalMonitorStateException is thrown.
- The wait() method cause the current thread to wait until it is woken up by a thread that calls notify() or notifyAll(). And while waiting, the thread can be interrupted by another thread. Hence we have to handle the InterruptedException.
- The notify() method causes the current thread to give up the lock so a randomly selected waiting thread is given the lock. That means there’s no guarantee that the thread that calls wait() is selected. It’s up to the thread scheduler.
- The notifyAll() method causes the current thread to release the lock and wakes up all other threads that are currently waiting. All threads have the chance.
2. Synchronized Blocks
In case you want to synchronize access at a smaller scope, i.e. a block of code rather than the whole method, you can use the
synchronized keyword like this:
public void update() {
synchronized (obj) {
// code block
}
}
A thread must hold the lock associated with the object
obj before it can execute the code block. The
obj can be any kind of object which you want to use it as a lock.And use the synchronized block with condition as follows:
synchronized (obj) {
if (!condition) {
obj.wait();
}
// code block
obj.notify();
// or:
obj.notifyAll();
}
By using synchronized blocks you have greater control over which part of the code should be serialized for access. For example, you can block concurrent access to the
write() method while allow the
read() method to be executed concurrently:
public class A {
private Object lock = new Object();
public void write() {
synchronized (lock) {
// code to write
}
}
public void read() {
// code to read
}
}
Here, you can see a pure Object is used as a lock. The
write() method can be executed by only one thread at a time, whereas the
read() method can be executed by multiple threads concurrently. This is possible because when a thread is executing the synchronized block, it doesn’t necessarily have to own the lock associated with the instance of the class. Instead, it holds the lock associated with the object protected by the synchronized block.Note that synchronizing a code block on the current instance of the class is equivalent to synchronizing an instance method. That means the following code:
public void update() {
synchronized (this) {
// code block
}
}
is equivalent to this:
public synchronized void update() {
// code block
}
Hence the Bank class can be rewritten using synchronized blocks on the
this instance. It’s your exercise.
3. Synchronized Static Methods
You can synchronize a static method and for that the threads have to acquire a different lock: the lock associated with the class itself (static), not an instance of the class (this).That means if you write:
public class A {
public static synchronized void update() {
// code
}
}
is equivalent to:
public class A {
public static void update() {
synchronized (A.class) {
// code
}
}
}
So when a thread is executing a synchronized static method, it also blocks access to all other synchronized static methods. The synchronized non-static methods are still executable by other threads. It’s because synchronized static methods and synchronized non-static methods work on different locks: class lock and instance lock.In other words, a synchronized static method and a non-static synchronized method will not block each other. They can run at the same time.That’s how the intrinsic (implicit) locking mechanism works in Java.
4. Explicit Locking vs. Intrinsic Locking
So far I have explained to you the work of two synchronization mechanism in Java:
- Explicit locking using Lock and Condition objects.
- Intrinsic locking using the synchronized keyword.
Now the question is: when to use which? When to use
Lock and when to use
synchronized?Here are some guidelines that help you make your decision:
- Consider using the synchronized keyword if you want to block concurrent access to instance methods (non-static synchronized methods) or static methods (static synchronized methods).
- Consider using explicit Lock and Condition objects if you want to have greater control over the synchronization process:
- Use more than one Condition objects associate with a Lock.
- Specify a timeout while a thread is waiting. This means the thread can wake up itself after a specified timeout expires.
Remember that using synchronized keyword is easier and less error-prone then using explicit lock. Using explicit lock gives you more control but you have to put more effort.
Related Tutorials:
Other Java Concurrency Tutorials:
About the Author:
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He started programming with Java in the time of Java 1.4 and has been falling in love with Java since then. Make friend with him on
Facebook and watch
his Java videos you YouTube.