This series of articles makes an attempt to help readers with the concept of locks in Java. It is assumed that the reader has some prior knowledge of Java and threads creation in Java.
Introduction
This article is in continuance with the previous article "Threading in Java: Object Locks II". In the previous example, we had created threads t1
and t2
out of the same processor object p1
and synchronized them with an object lock objLock
which was a member variable of class processor and instantiated as a part of p1
. Thus both threads t1
and t2
could access the same objLock
because both threads t1
and t2
were created out of p1
as shown in the below code snippet:
processor p1 = new processor();
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p1, "t2");
Let's squeeze some lemon to add a tangy taste to this program. Think what would happen if we created t2
out of a new object p2
instantiated out of processor? The program would look like this:
class processor implements Runnable{
Object obj;
public processor(){
obj = new Object();
}
public void run() {
display();
}
public void display() {
synchronized(obj) {
for (int i = 0 ; i<= 5; i++) {
System.out.println("i = " + i + " In thread" + Thread.currentThread());
}
}
}
}
class locks {
public static void main(String[] args) {
processor p1 = new processor();
processor p2 = new processor();
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p2, "t2");
t1.start();
t2.start();
}
}
Can you guess what would be the output of the program? Would it be a regular output or would it be an irregular interleaved output full of printfs
from t1
and then t2
and then t1
and so on and so forth?
Well... the above program compiles and runs successfully, but check what output we got!
i = 0 In threadThread[t2,5,main]
i = 0 In threadThread[t1,5,main]
i = 1 In threadThread[t1,5,main]
i = 1 In threadThread[t2,5,main]
i = 2 In threadThread[t1,5,main]
i = 2 In threadThread[t2,5,main]
i = 3 In threadThread[t1,5,main]
i = 3 In threadThread[t2,5,main]
i = 4 In threadThread[t1,5,main]
i = 4 In threadThread[t2,5,main]
i = 5 In threadThread[t1,5,main]
i = 5 In threadThread[t2,5,main]
If you observe carefully, we got an interleaved, irregular output. Well, if we had an object lock, why did we get an interleaved output? The answer to this is simple. Well, we do have an object lock. But in this case, the objectLock
for p1
and objectLock
for p2
are different and not common. The object p1
created out of processor()
has its own instance of objectLock
which is totally different from objectLock
of p2
created out of processor()
because p1
and p2
are two different objects now. There is no race condition to acquire a same lock. Thread t2
acquires its own lock and thread t1
acquires its own lock and they proceed. Hence, we get an interleaved output.
So what do we do to get a synchronized output? In order to get a synchronized output, we need to ensure that both the threads have access to the same lock. This is done in the following example:
class processor implements Runnable{
Object objLock;
public processor(Object objLock){
this.objLock = objLock;
}
public void run() {
display();
}
public void display() {
synchronized(objLock) {
for (int i = 0 ; i<= 5; i++) {
System.out.println("i = " + i + " In thread" + Thread.currentThread());
}
}
}
}
class locks {
public static void main(String[] args) {
Object objLock = new Object();
processor p1 = new processor(objLock);
processor p2 = new processor(objLock);
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p2, "t2");
t1.start();
t2.start();
}
}
Output
=======
i = 0 In threadThread[t1,5,main]
i = 1 In threadThread[t1,5,main]
i = 2 In threadThread[t1,5,main]
i = 3 In threadThread[t1,5,main]
i = 4 In threadThread[t1,5,main]
i = 5 In threadThread[t1,5,main]
i = 0 In threadThread[t2,5,main]
i = 1 In threadThread[t2,5,main]
i = 2 In threadThread[t2,5,main]
i = 3 In threadThread[t2,5,main]
i = 4 In threadThread[t2,5,main]
i = 5 In threadThread[t2,5,main]
The above program compiles and runs successfully to produce the desired synchronized output. For thread t1
and t2
to have the same objLock
, we created it in main()
and then passed it to processor objects p1
and p2
through its constructors. Now both p1
and p2
share the same objLock
. During the race condition in the display()
method for the forloop, t1
acquires the lock to objLock
. Hence t2
has to wait until t1
releases the lock. t2
gets an opportunity to enter the forloop only after t2
has released the lock. This way, we get a synchronized output. Hope that makes this clear on how object locks are working. Two or more threads will synchronize if they have one and only one object lock in common.
Points of Interest
Two or more threads will synchronize if they have one and only one object lock in common.
History
- 15th June, 2021: Initial revision