Java Synchronization Tutorial Part 3 - Using synchronized keyword (Intrinsic locking)
- 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:
- Java Synchronization Tutorial Part 1 - The Problems of Unsynchronized Code
- Java Synchronization Tutorial Part 2 - Using Lock and Condition Objects
- Java ReadWriteLock and ReentrantReadWriteLock Example
Other Java Concurrency Tutorials:
- How to use Threads in Java (create, start, pause, interrupt and join)
- Understanding Deadlock, Livelock and Starvation with Code Examples in Java
- Understanding Java Fork-Join Framework with Examples
- Understanding Atomic Variables in Java
- Understand Thread Pool and Executors
Comments
As I read from Javadocs, it's as below:
WAITING: Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:
Object.wait with no timeout
So I think its STATE would be WAITING when it's waiting. Please re-confirm that for me. And thank you so much for your valuable contents. Best Regard.
It would be Runnable. Note that runnable doesn't necessarily mean 'running'.
A thread can come back to runnable state from another state (waiting, blocked), but it may not be picked immediately by the thread scheduler, hence the term “runnable”, not running.
Do you know to which state the waiting thread goes to, when it is notified from other thread?
It would not be Runnable, since the lock is not yet released, it also would not be in waiting state since it is notified.
Any thoughts?
Yes, I confirm that is correct. The notify() and notifyAll() methods just send signal to the thread scheduler. And it's up to the scheduler to choose which thread and when to react.
Small correction:
As far as I've read in books, notify and notifyAll methods doesn't release the lock just after it is executed, like wait does.
The thread needs to completely come out of synchronized block, in order to release the lock, even after notify or notifyAll is called. Notify would just signal the waiting threads to wake up.
Can you reconfirm on this?
Thanks,
Gowtham