This is the 10th day of my participation in the August More Text Challenge. For details, see:August is more challenging
Lightweight lock positioning
The purpose of lightweight locks is to minimize the use of operating system-level mutex, which can be poor in performance. Thread blocking and waking requires the CPU to change from the user state to the core state. Frequent blocking and waking is a heavy work for the CPU.
If many object locks are locked for only a short period of time, such as integer self-add operations, it is not worth blocking and waking up the thread for a short period of time, so lightweight locks are introduced. A lightweight lock is a type of spin lock.
Lightweight lock acquisition process
- Before the snatch thread enters the critical section, if the lightweight Lock is not occupied, the JVM will first create a Lock Record in the snatch thread’s stack frame to store the objects associated with the lightweight Lock
Mark Word
A copy of the.
-
The thread preempting the lightweight Lock uses CAS spin to attempt to update the ptr_to_LOCK_record of the Mark Word in the built-in Lock object header to the address of the Lock Record in the stack frame of the Lock thread. If this update is successful, The thread then owns the object lock.
-
The JVM changes the LOCK flag bit in Mard Word to 00(lightweight lock flag), indicating that the object is in a lightweight lock state
-
The JVM takes the original lock object information (such as hashcode, etc.) in the Mard Word of the object associated with the lightweight lock, This is because the object header of the Lock object needs to store the LockRecord address of the preempt thread, there is no place to store their own hashcode 🙂 is occupied
Lightweight Lock Example
Or a biased Foo instance, see juejin.cn/post/699440…
The test code
@Test
public void test(a) throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
Foo foo = new Foo();
System.err.println(ClassLayout.parseInstance(foo).toPrintable());
foo.incr();
new Thread(new LockTest(foo)).start();
TimeUnit.SECONDS.sleep(2);
System.err.println(ClassLayout.parseInstance(foo).toPrintable());
foo.incr();
}
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
main-org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007fc2e7809005 (biased: 0x0000001ff0b9e024; epoch: 0; age: 0)
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-1-org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007000051a6918 (thin lock: 0x00007000051a6918)
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
main-org.ywb.Foo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000070000395a898 (thin lock: 0x000070000395a898)
8 4 (object header: class) 0xf8011d21
12 4 int Foo.i 3
Instance size16:bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Copy the code
-
The result of the first print is that the object is not biased to any thread yet, just to lock
-
The result of the second print is when the thread is executing the synchronized block, at which point the bias lock stores the bias thread ID and the bias timestamp
-
The third time, the biased lock preempted, and the biased lock became a lightweight lock
-
On the fourth print, when both threads have executed the synchronization code, the lock is released and the object head is locked
-
When the synchronized block is called again, the lock becomes a lightweight lock, verifying that the lock can only be upgraded, not degraded.
Classification of lightweight locks
Ordinary spin lock
A common spin lock means that when a thread is competing for the lock, the snatch thread waits in place, rather than being blocked, until the thread that owns the lock releases the lock, and the snatch thread can immediately acquire the lock. By default, the number of spins is 10, which can be changed with the -xx :PreBlockSpin option.
Spin is also CPU intensive, but if a thread takes a long time to execute a synchronized block of code, the thread preempting the lock will often use up all of its spins without acquiring a lightweight lock and end up upgrading to a heavyweight lock. In this case, spin is meaningless.
Adaptive spin lock
The adaptive spin lock means that the number of spin waits is not fixed, but will dynamically change the number of spin waits according to the actual situation. The number of spin waits is determined by the previous spin time on the same lock and the state of the lock owner.
The general principle is as follows:
-
If the lock thread has previously successfully acquired a lock on the same lock object, the JVM will assume that the spin is likely to succeed again, and will allow the spin wait to last a relatively longer time.
-
If the lock grab thread has rarely successfully acquired a lock, the JVM may reduce spin time or even omit the spin process to avoid wasting processor resources.
The lightweight lock of JDK1.6 uses a common spin lock, and you need to manually enable the -xx :+UseSpinning option.
After JDK1.7, lightweight locks use adaptive spin locks, which are automatically enabled when the JVM starts, and the spin time is automatically controlled by the JVM.