Java monitor and lock
In this article, we will go in depth on the terms monitor and lock in java and the conceptual difference between them. This is a confusing concept for the readers and it a frequent interview question as well.
To understand these fully, we will also cover terms such as thread synchronization, race condition, critical section etc.
Remember that all these programming concepts not limited to java language. This article will explain these concepts with examples written in java but these are equally relevant to non-java programmers as well.
A thread is smallest unit of execution. A thread allows multiple tasks to execute concurrently in an application.
Consider a client application which receives request from a server for processing, sends periodic health check messages to the server, writes some logs to a database or files.
If the client is single threaded, then it can not receive request while it is sending health check message or writing to database. This is not desirable.
With multiple threads, there is a new thread for each server request, a single worker thread that sends health check messages and another thread for writing logs.
When multiple threads are executing in parallel, this is called concurrent execution or concurrent programming or concurrency. Main spot light word is concurrent, meaning, at the same time.
Mutual Exclusion
Mutual exclusion is a characteristic of concurrent applications where only one thread is allowed to access shared resources of an object.
Any other thread which needs to access the resource of the same object, then it has to wait till the first thread completes execution.
When related to programming, shared resource means instance or class variables and methods. Below is an example of a java class to understand shared resources.
public class Counter { private int count; public void add() { count++; } public void subtract() { if(count > 0) { count--; } } public int getCount() { return count; } }
Here, add(), subtract() and getCount() methods, are shared resources.
Mutual exclusion is required to prevent race condition and to ensure that only one thread is allowed to enter critical section.
Imagine the counter object above which increases and decreases a counter. Suppose two threads are using the same counter object with count currently set to 1.
Both threads invoke subtract() at the same time. Each thread finds the value of count to be greater than 0 and decreases it by 1. This makes the count negative, which should not be the case.
Reason is that both threads executed subtract() or shared resource concurrently.
This is called race condition, where more than one threads access a shared resource and the result is dependent on the order of execution of the threads.
Race condition leads to data inconsistency and abnormal behavior.
Remember that mutual exclusion is only required when multiple threads are working on the same object of a class.
If the objects are different, there is no need of mutual exclusion, since each object has its copy of methods and variables.
Critical Section
In a multi-threaded application, multiple threads might use the same object. Thus, methods and variables of an object are shared among these threads.
When these methods are executed by different threads at the same time, there are chances of data inconsistency due to race condition, as we saw above.
Thus, the methods and variables which we want, should be executed by a single thread at a time, need to be protected.
This protected section is called critical section of an object.
In the Counter
class in previous section, add() and subtract() methods should be executed by a singgle thread at the same time, so this is the Critical section.
In java objects, methods are protected from concurrent access using synchronized keyword. Updated Counter
class with protected areas or critical sections defined is given below
public class Counter { private int count; public synchronized void add() { count++; } public synchronized void subtract() { if(count > 0) { count--; } } public int getCount() { return count; } }
synchronized
is a java specific construct, other languages too have similar way of protecting concurrent access.
Locks
A lock is a mechanism which is used to achieve mutual exclusion of threads. A lock is also called mutex.
A thread must possess the lock of an object before accessing its protected data or critical section. The thread acquires the object lock before entering its critical section and releases the lock upon exit.
While a thread is executing the protected code of an object, lock guarantees that no other thread can execute it.
Each object has only one lock so if a thread acquires the lock, no other thread will get it, till the first thread releases it.
If any other thread needs to execute synchronized code or critical section, then it is sent to waiting state.
Acquiring the lock is also called getting the lock, locking the object, synchronizing on object. All these phrases mean the same.
Each object has a lock associated with it. A monitor is the object whose lock is acquired by the thread.
A monitor is the thread safe class or object.
So, in our
Counter
class, an object of this class is a monitor which has a lock that is used to achieve mutual exclusion.
In simple terms, you can say that a monitor contains the lock or monitor is a wrapper around the lock. The terms lock and monitor are used interchangeably but technically they are different.
Besides lock, a monitor also contains a waiting area or wait set which facilitates thread cooperation. Thread cooperation is required when multiple threads are working together or when one thread is dependent on other.
Example,
Consider two threads where one is writing a record to the database and other thread is reading it. Second thread canot proceed till the first thread inserts in the database.
This is referred to as producer/consumer problem.
Thread cooperation in java is achieved using wait(), notify() methods. wait() method is invoked on an object(the monitor).
When wait() is invoked, the current executing thread goes to the waiting area till some other thread calls notify() on the same object.
Below is an example code snippet
class DbUtil { public void read() { try { // wait till other thread finishes writing wait(); // perform read } catch (InterruptedException e) { e.printStackTrace(); } } public void write() { // perform write // inform waiting thread notify(); } }
Reading thread calls wait() and goes into waiting state till the writing thread calls notify(), after which the reader thread proceeds with reading.
Code example is written in java but the concept of thread cooperation is same in all languages.
Thus, a monitor allows threads to have mutual exclusion through locks and cooperation through its waiting area.
Monitor vs Lock
If you have read the article till this point, you will be able to tell the difference between monitor and lock in a multi-threaded environment.
Below are the summary points
- Each object is associated with a lock. A lock ensures that only one thread executes shared and protected code.
- A lock is used for mutual exclusion of multiple threads.
- A monitor is an object or class or module that contains lock.
- A monitor also has a waiting area which is used for thread coordination or inter-thread communication.
Below image will provide a more clear picture about Monitor, Lock and other concepts outlined in this article.
Do not forget to click the clap if the article was useful.