This is the 11th day of my participation in the August Wenwen Challenge.More challenges in August
Lightweight lock is suitable for the critical area code execution time is very short, if the critical area code execution time is very long, so it will cause other threads for lock will CPU idle for a long time, which run counter to the design of lightweight lock, therefore, if the critical section of code execution if a long time, Using mutex to suspend lines competing for locks performs better than lightweight locks.
The principle of heavyweight locking
In the Java Virtual machine, the JVM associates each object with a Monitor object, which is created as the object is created and destroyed as the object is destroyed. This Monitor object is used to ensure that only one thread can access a critical resource at a time.
The main role of Monitor
1. The synchronous
Monitor is a “license” that any thread that wants to access a critical resource must obtain and return when it has accessed the critical resource.
2. The collaboration
Monitor also provides a mechanism for collaboration. A thread executing synchronized code can release its license and sleep, and other threads can acquire the license again and wake up the dormant thread to achieve thread collaboration.
The structure of Monitor objects in HotSpot
The openJDK objectMonitor. HPP file shows the structure of Monitor in the VM as follows:
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0;
// The number of threads reentrant
_recursions = 0;
_object = NULL;
// Identifies the thread that owns the monitor
_owner = NULL;
// A bidirectional circular linked list of waiting threads
_WaitSet = NULL;
_WaitSetLock = 0;
_Responsible = NULL;
_succ = NULL;
// multi-threaded contention lock entry is a one-way linked list
cxq = NULL;
FreeNext = NULL;
// _owner wakes up the thread node from the two-way circular list
_EntryList = NULL;
_SpinFreq = 0;
_SPinClock = 0;
OwnerIsThread = 0;
}
Copy the code
Introduction to core attributes
- Owner Specifies the thread that holds the lock
- Recursions Number of reentries for the recursions thread
- WaitSet The queue to which a thread is placed after the wait method is called
- The queue in which the CXQ thread requesting the lock is placed first
- EntryList Specifies the queue to which the threads in the CXQ queue that are eligible to acquire locks are moved
Thread preemption lock process
-
Cxq competing queues
Each new Node is added in the Cxq queue header. CAS is used to change the pointer of the first Node to the new Node and set the next of the new Node to point to subsequent nodes. When elements are fetched from Cxq, they are fetched from the tail of the queue. Since only the Owner thread can fetch elements from the end of the queue, there is no contention for thread exit operations, and of course the ABA problem with CAS is avoided. Before the thread enters Cxq, the lock grab thread will try to obtain the lock through CAS spin. If it fails to obtain the lock, it will enter the Cxq queue, which is obviously unfair to the thread that has entered the Cxq queue. Therefore, heavyweight locks used by synchronized blocks are unfair locks.
-
EntryList
EntryList and Cxq logically belong to wait queues. Cxq is accessed concurrently by threads, and EntryList is created to reduce contention for the Cxq tail. When the Owner Thread releases the lock, the JVM migrates threads from Cxq to EntryList and specifies one of the EntryList threads (typically Head) as OnDeck Threads (Ready Threads). Threads in EntryList exist as candidate contention threads.
-
OnDeck Thread and Owner Thread
Instead of passing the lock directly to the Owner Thread, the JVM hands the lock contention to the OnDeck Thread, which needs to re-compete for the lock. This sacrifices some fairness, but can greatly improve the throughput of the system. In the JVM, this choice behavior is also called “competitive switching.” Another unfair thing that happens when the OnDeck Thread becomes the Owner is that a later new lock grab Thread might grab the lock by simply spinning into the Owner of the CAS
-
WaitSet
The Owner thread is blocked by object.wait () and is moved to the WaitSet queue until it is awakened by Object.notify() or Object.notifyAll(), at which point it re-enters the EntryList.
Heavyweight lock Demo
@Test
public void test2(a) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
Foo foo = new Foo();
System.err.println(ClassLayout.parseInstance(foo).toPrintable());
new Thread(new LockTest(foo)).start();
new Thread(new LockTest(foo)).start();
TimeUnit.SECONDS.sleep(2);
System.err.println(ClassLayout.parseInstance(foo).toPrintable());
}
Copy the code
Print result:
org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf8011d21
12 4 int Foo.i 0
Instance size16:bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Thread-1-org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007fca4b80d3da (fat lock: 0x00007fca4b80d3da)
8 4 (object header: class) 0xf8011d21
12 4 int Foo.i 1
Instance size16:bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Thread-2-org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007fca4b80d3da (fat lock: 0x00007fca4b80d3da)
8 4 (object header: class) 0xf8011d21
12 4 int Foo.i 2
Instance size16:bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf8011d21
12 4 int Foo.i 2
Instance size16:bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Copy the code
Explanation:
- The first time it prints, the object is in bias lock, but it is not biased to a thread, so the bias thread ID and bias timestamp are empty
The lock ballooned into a heavyweight lock because two threads started at the same time and fought for it at the same time
- For the second print, the thread name is
Thread-1
The thread of “, “acquires the heavyweight lock and increments the variable by one - For the third print, the thread name is
Thread-2
The thread has acquired the threadThread-1
Lock, increment the variable by 1 - The fourth time the object is unlocked because both threads have released the heavyweight lock.
The overhead of heavyweight locks
The ContentionList, EntryList and WaitSet threads are all blocked. The blocking or waking up of threads requires the help of the operating system, which is implemented by the Pthread_mutex_lock system call in the Linux kernel. The pthread_mutex_lock system call is a Linux Mutex access mechanism provided by the kernel for the user process. Therefore, when using the pthread_mutex_lock system call, Processes need to switch from user to kernel mode, and this switch takes a lot of time, possibly longer than the user executes the code.