Java Multithreading High Concurrency Advanced Chapter (2) - Analysis of the Implementation Principle of Synchronized

In multithreaded concurrent programming, thread safety is the focus of attention. Generally speaking, the use of multi-threaded programming is to achieve higher execution efficiency. If even the most basic data accuracy cannot be guaranteed, there is no point in talking about concurrency.

1. Why is synchronized lock (synchronized lock) a heavyweight lock?

When it comes to high-concurrency multi-threaded programming, it is estimated that the one that you are most exposed to is synchronized, which is a veteran-level role in concurrent programming. We call synchronized locks (synchronized locks) heavyweight locks because they cause threads that cannot contend for locks to block, and blocking or waking up a thread requires the operating system to intervene, and it needs to be between user mode and kernel mode. Conversion, and this conversion process requires a lot of resources (because user mode and kernel mode have their own memory space, in the process of state switching, user mode needs to send parameter variables and other content to kernel mode, and kernel mode needs to record The state of the user mode during the switching process).

 

2. The realization principle of synchronization lock

Before talking about the implementation principle, we need to know the implementation basis of synchronization locks: every Java object can be used as a lock! Why do you say that? Let's start with the basics.

1. According to the scope of the synchronization lock, the synchronization lock can be divided into:

①Object lock: To lock a given object, you need to acquire the lock of this object before entering the code block.

②Instance lock: applied to the instance method in the class, the lock is the current instance object.

③Class lock: the static method acting on the class, the lock is the Class object of the current class.

 

Let's start with a small piece of code and look at its bytecode.

 

public class SynchronizedDemo {
	Object object = new Object();
	public void testObjectLock() {
		synchronized (object) {
				System.out.println("Object lock");
	    }
	}
	
	public synchronized void testInstanceLock() {
		System.out.println("Instance lock");
	}
	
	public static synchronized void testClassLock() {
		System.out.println("Class lock");
	}
}

 

Let's take a look at how object locking is represented in bytecode.


 In the bytecode, we find a pair of monitorenter and monitorexit instructions.

By the way, the synchronization implementation of object locks is implemented using monitorenter and monitorexit instructions, while instance locks and class locks are implemented in another way. The details are not detailed in the JVM specification, but it is clear that instance locks are definitely It can also be implemented in this way.

 

2. Let's take a look at the description of these two instructions in the JVM specification:

The JVM spec writes about monitorenter 
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

Let's explain this passage (you can use Google Translate), and explain the monitorenter as follows:

Each lock object is associated with a monitor. When the monitor is occupied, it is locked. When a thread executes the monitorenter instruction, it attempts to acquire ownership of the monitor associated with the lock object, as follows:

① If the counter value of the monitor associated with the lock object is 0, the thread takes ownership of the monitor and sets the monitor's counter value to 1. 

② If the thread already owns the monitor associated with the lock object, it will re-enter the monitor (that is, regain ownership of the monitor), and then increment the monitor's counter value by 1.

③ If another thread already owns the monitor associated with the lock object, the thread blocks until the monitor's counter value becomes 0, and then tries to acquire ownership.

The JVM spec writes about monitorexit
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

The explanation for monitorexit is as follows: 

The thread executing the monitorexit instruction must be the owner of the monitor corresponding to the lock object. When the instruction is executed, the counter of the monitor is decremented by 1. If the entry count is 0 after decrementing by 1, the thread exits the monitor and is no longer the owner of the monitor. Other threads blocked by this monitor can try to take ownership of this monitor. 

 

From the implementation description required by the JVM specification, we can see that the way to implement object locks is to use the monitorenter and monitorexit instructions.

This also explains a sentence: every object can exist as a lock object!

In the next post, we will introduce where the lock information is stored, as well as the lock upgrade (also called expansion) process and comparison.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326002064&siteId=291194637