21 How to See the Monitor Lock Behind synchronized #
In this lesson, we will study the monitor lock behind synchronized.
Timing of acquiring and releasing the monitor lock #
We all know that the simplest way to synchronize is to use the synchronized keyword to modify a code block or a method. In this protected code, at most one thread can run at the same time, and this is achieved by using the monitor lock behind synchronized. So first let’s look at the timing of acquiring and releasing the monitor lock. Each Java object can be used as a lock for synchronization. This lock is also called an intrinsic lock or monitor lock. The only way to obtain the monitor lock is to enter the synchronized code block or synchronized method protected by this lock. Before a thread enters a code block protected by synchronized, it automatically acquires the lock, and whether it exits through a normal path or by throwing an exception, it automatically releases the lock upon exit.
First, let’s look at an example of a method that is synchronized:
public synchronized void method() {
method body
}
We can see that the method() method is synchronized. To better understand the underlying principle, let’s rewrite the above code into the following equivalent pseudocode:
public void method() {
this.intrinsicLock.lock();
try{
method body
}
finally {
this.intrinsicLock.unlock();
}
}
In this writing, as soon as we enter the method() method, we immediately acquire the intrinsic lock, and use the try code block to protect the method, and finally release the lock using the finally block. Here, intrinsicLock
is the monitor lock. After expanding the pseudocode in this way, I believe your understanding of synchronized will be clearer.
Use the javap command to view the disassembled result #
The JVM implements the details of synchronized methods and synchronized code blocks differently. Now let’s look at the implementation of the two separately.
Synchronized code block #
First, let’s look at the implementation of a synchronized code block, as shown in the code:
public class SynTest {
public void synBlock() {
synchronized (this) {
System.out.println("lagou");
}
}
}
In the synBlock
method of the SynTest
class, there is a synchronized code block, and the synchronized code block contains a line of code that prints the string “lagou”. Now let’s see what the synchronized keyword really does using the following command: first use the cd command to switch to the directory where the SynTest.java class is located, then run javac SynTest.java
, and a bytecode file named SynTest.class will be generated. Then we execute javap -verbose SynTest.class
, and we will see the corresponding disassembled content.
Key information is as follows:
public void synBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String lagou
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
From the code, we can see that the synchronized block actually has two instructions: monitorenter
and monitorexit
. The highlighted instructions on lines 3, 13, and 19 correspond to the monitorenter
and monitorexit
instructions respectively. The reason for one monitorenter
and two monitorexit
instructions is that the JVM ensures that every monitorenter
must have a corresponding monitorexit
. The monitorenter
instruction is inserted at the beginning of the synchronized block, while monitorexit
needs to be inserted at the normal end and the exception end of the method in order to ensure that the lock is released even in case of exceptions.
We can interpret the execution of monitorenter
as locking and the execution of monitorexit
as unlocking. Each object maintains a counter that records the number of times it has been locked. An object that is not locked has a counter value of 0. Let’s take a closer look at the meaning of monitorenter
and monitorexit
:
-
monitorenter
: The thread executingmonitorenter
attempts to acquire ownership of the monitor. There are three possible situations: a. If the counter for the monitor is 0, the thread acquires the monitor and sets its counter to 1. The thread becomes the owner of the monitor. b. If the thread already owns the monitor, it re-enters and increments the counter. c. If another thread already owns the monitor, the current thread is blocked until the counter for the monitor becomes 0, indicating that the monitor has been released. The current thread then tries to acquire the monitor again. -
monitorexit
: The purpose ofmonitorexit
is to decrement the counter for the monitor until it reaches 0. This indicates that the monitor has been released and no thread owns it. It represents the unlock operation. Therefore, other threads waiting for the monitor can now try to acquire ownership of it again.
Synchronized Methods #
From the above, we can see that a synchronized block is implemented using monitorenter
and monitorexit
instructions. On the other hand, synchronized methods are not implemented using monitorenter
and monitorexit
instructions. When decompiled with javap
, synchronized methods appear very similar to ordinary methods, with the difference being that they have a flag modifier called ACC_SYNCHRONIZED
to indicate that they are synchronized methods.
Here is an example of a synchronized method:
public synchronized void synMethod() {
}
The corresponding decompiled instructions are as follows:
public synchronized void synMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 16: 0
As we can see, synchronized methods have an ACC_SYNCHRONIZED
flag. When a thread wants to access a method, it first checks if the method has the ACC_SYNCHRONIZED
flag. If it does, the thread needs to acquire the monitor lock before it can start executing the method. After the method is executed, the monitor lock is released. In other aspects, synchronized methods are very similar to synchronized blocks. For example, if another thread tries to execute the method while the monitor lock is held, it will be blocked because it cannot acquire the monitor lock.
That’s it for this lesson. In this lesson, we discussed the timing of acquiring and releasing monitors, equivalent code for synchronized methods, and used the javac
and javap
commands to inspect the disassembled instructions for synchronized blocks and synchronized methods, where synchronized blocks are implemented using monitorenter
and monitorexit
instructions, while synchronized methods are implemented using flags.