Synchronized object head test under different conditions

The test environment

JDK: Oracle JDK 1.8.0_144

Code dependencies:

  • Junit – Jupiter – engine: 5.8.1
  • Slf4j – simple: 1.7.32
  • Jol – core: 0.16

The test code

import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openjdk.jol.info.ClassLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class LockObject {}

class SyncTest {

	private static final Logger log = LoggerFactory.getLogger(SyncTest.class);

	@Test
  void testSynchronizedLock(a) throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
  }

  void syncLock(Object lock) {
    log.info("Before locking {}", ClassLayout.parseInstance(lock).toPrintable());
    synchronized (lock) {
    	log.info("Locking {}", ClassLayout.parseInstance(lock).toPrintable());
    }
    log.info("After lock {}", ClassLayout.parseInstance(lock).toPrintable()); }}Copy the code

Test case

TestSynchronizedLock is tested by modifying the testSynchronizedLock method code. The following test cases illustrate only the modified testSynchronizedLock code. The rest of the values are omitted because only the object header changes.

Case 1: Call directly from the same thread

void testSynchronizedLock(a) throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
}
Copy the code

The execution result is as follows:

Before locking 0x0000000000000001 (Non-Biasable; Age: 0) Locked medium 0x0000700007830F10 (thin Lock: 0x0000700007830F10) Locked after 0x0000000000000001 (Non-Biasable; age: 0)Copy the code

According to the result, the object header is 0x0000000000000001 before locking, 0x0000700007830F10 after locking, and 0x0000000000000001 after locking. This may seem confusing, but here’s a brief description of the distribution of object headers for 64-bit JVMS

|--------------------------------------------------------------------------------------------------------------| | Object Header (128 bits) | |--------------------------------------------------------------------------------------------------------------| | Mark Word (64 bits) | Klass Word (64 bits) | |--------------------------------------------------------------------------------------------------------------| | Unused: 25 | identity_hashcode: 31 | unused: 1 | age: 4 | biased_lock: 1 the lock: | 2 | OOP to the metadata object | unlocked |----------------------------------------------------------------------|--------|------------------------------| | Thread: 54 | epoch: 2 | unused: 1 | age: 4 | biased_lock: 1 the lock: | 2 | OOP to the metadata object | biased locking |----------------------------------------------------------------------|--------|------------------------------| | Ptr_to_lock_record: the lock: 62 | 2 | OOP to the metadata object | lightweight lock |----------------------------------------------------------------------|--------|------------------------------| | Ptr_to_heavyweight_monitor: the lock: 62 | 2 | OOP to the metadata object lock | weight |----------------------------------------------------------------------|--------|------------------------------| | | lock:2 | OOP to metadata object | GC |--------------------------------------------------------------------------------------------------------------|Copy the code

Lock: indicates the mark of the lock status. Different values of the mark indicate different meanings.

Biased_lock: Bias lock flag. A value of 1 indicates that biased locks are enabled on the object, and a value of 0 indicates that biased locks are not enabled on the object.

From the distribution, it can be concluded that, look at the lock mark, directly look at the last 3 bits

biased_lock lock hexadecimal state
0 01 1 unlocked
1 01 5 bias
0 00 0 lightweight
0 10 2 The weight of the
0 11 3 GC

The object header is 0x0000000000000001 before the lock, 0x0000700007830F10 in the lock, and 0x0000000000000001 after the lock

From this situation, it can be seen that the object is in the unlocked state before locking, in the light lock state during locking, in the unlocked state after releasing the lock

This phenomenon may be different from what we imagine. I found the information on the Internet as follows:

The JVM starts up with a series of complex activities, such as loading configuration, system class initialization, and so on. In this process, a large number of synchronized keywords are used to lock objects, and most of these locks are not biased locks. To reduce initialization time, the JVM defaults to lazy loading bias locks. The delay time is about 4s, which varies from machine to machine. Of course we can also set the JVM parameter – XX: BiasedLockingStartupDelay = 0 to cancel delay loading biased locking.

As you can see from the above, the JVM defaults to delayed load bias locking with a time greater than 4s. For better verification, the following code is directly processed at 10s.

Case 2: The lock is acquired once and then acquired again 10 seconds later

This is just to verify the above conclusion

void testSynchronizedLock(a) throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    TimeUnit.SECONDS.sleep(10);
    syncLock(lock);
    Assertions.assertTrue(true);
}
Copy the code

The following output is displayed:

Before locking 0x0000000000000001 (Non-Biasable; Age: 0) Locked in 0x00007000028AAF10 (thin Lock: 0x00007000028AAF10) Locked in 0x0000000000000001 (Non-Biasable; Age: 0) before lock 0x0000000000000001 (Non-Biasable; Age: 0) Locked in 0x00007000028AAF10 (thin Lock: 0x00007000028AAF10) Locked in 0x0000000000000001 (Non-Biasable; age: 0)Copy the code

A lightweight lock used for both lock retrieves

Case 3: Call 10 seconds later after the lock object is created

void testSynchronizedLock(a) throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
}
Copy the code

The log is as follows:

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x00007FB114010805 (Biased: 0x0000001fec450042; epoch: 0; Age: 0) Locked 0x00007FB114010805 (Biased: 0x0000001fec450042; epoch: 0; age: 0)Copy the code

From this situation, we can see that the object is in the biased lock state before locking, in the biased lock state during locking, in the biased lock state after releasing the lock, but before locking, there is no biased to any thread

Situation 4: increase BiasedLockingStartupDelay = 0 parameter

void testSynchronizedLock(a) throws InterruptedException {
  Object lock = new Object();
  syncLock(lock);
  Assertions.assertTrue(true);
}
Copy the code

The log

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x00007FD650009005 (Biased: 0x0000001FF5940024; epoch: 0; Age: 0) Locked 0x00007FD650009005 (Biased: 0x0000001FF5940024; epoch: 0; age: 0)Copy the code

From this situation, we can see that the object is in the biased lock state before locking, in the biased lock state during locking, in the biased lock state after releasing the lock, but before locking, there is no biased to any thread

It can be concluded from the above four cases:

By default, the JVM delays enabling biased locking, and lock objects created before biased locking is enabled by the JVM start acquiring locks using lightweight locks instead of going through the lightweight lock phase. If close the delay function, you can use – XX: BiasedLockingStartupDelay = 0 parameter

Use timeunit.seconds.sleep (10) for the later test case; To implement and – XX: BiasedLockingStartupDelay = 0

Case 5: Multiple calls from the same thread

void testSynchronizedLock(a) throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    syncLock(lock);
    Assertions.assertTrue(true);
}
Copy the code

The log is as follows:

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x0000023099602005 (Biased: 0x000000008C265808; epoch: 0; Age: 0) Locked 0x0000023099602005 (Biased: 0x000000008C265808; epoch: 0; Age: 0) Before locking 0x0000023099602005 (Biased: 0x000000008C265808; epoch: 0; Age: 0) Lock 0x0000023099602005 (Biased: 0x000000008C265808; epoch: 0; Age: 0) Locked 0x0000023099602005 (Biased: 0x000000008C265808; epoch: 0; age: 0)Copy the code

It can be seen from the log that the bias lock used in the first locking is biased to 0x000000008C265808 after locking. When the bias lock is used in the second locking, because it is still in the same thread, the bias lock is still pointed to the same, so the lock is directly acquired without locking upgrade.

Case 6: Multiple threads call twice without contention

void testSynchronizedLock(a) throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    Assertions.assertTrue(true);
}
Copy the code

The log is as follows:

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x00000264E4573005 (Biased: 0x00000000993915cc; epoch: 0; Age: 0) Locked 0x00000264E4573005 (Biased: 0x00000000993915cc; epoch: 0; Age: 0) Before locking 0x00000264E4573005 (Biased: 0x00000000993915CC; epoch: 0; Age: 0) Locked in 0x000000D0C6DFF748 (thin Lock: 0x000000D0C6DFF748) Locked in 0x0000000000000001 (non-Biasable; age: 0)Copy the code

Can be seen from the log, lock for the first time, the use of biased locking, lock for the second time when use of lightweight lock when binary 1000 (8), we can conclude that even in the absence of competition, as long as there is a thread with a lock, that another thread lock will turn into a lightweight lock, can be seen from the last log, and eventually become locked state

Case 7: Multiple threads are called many times without competing

void testSynchronizedLock(a) throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    syncLock(lock);
    thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    Assertions.assertTrue(true);
}
Copy the code

The log

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x0000028212C72005 (Biased: 0x00000000a084b1C8; epoch: 0; Age: 0) Locked 0x0000028212C72005 (Biased: 0x00000000a084B1C8; epoch: 0; Age: 0) Before locking 0x0000028212C72005 (Biased: 0x00000000a084B1C8; epoch: 0; Age: 0) Locked 0x000000CA803FeFD8 (thin Lock: 0x000000CA803FeFD8) Locked 0x0000000000000001 (non-Biasable; Age: 0) before lock 0x0000000000000001 (Non-Biasable; Age: 0) Locked in 0x000000cafeEFC8A0 (thin Lock: 0x000000cafeEFC8A0) Locked in 0x0000000000000001 (non-Biasable; Age: 0) before lock 0x0000000000000001 (Non-Biasable; Age: 0) Locked in 0x000000CA803FF308 (thin Lock: 0x000000CA803FF308) Locked in 0x0000000000000001 (non-Biasable; age: 0)Copy the code

The result is proof that lightweight locks can be converted to lockless

Case 8: Multiple threads have competing calls

void testSynchronizedLock(a) throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    Thread thread2 = new Thread(() -> syncLock(lock));
    thread.start();
    thread2.start();

    thread.join();
    thread2.join();
    Assertions.assertTrue(true);
}

void syncLock(Object lock) {
    log.info("currentThread {}", Thread.currentThread().getId());
    log.info("Before locking {}", ClassLayout.parseInstance(lock).toPrintable());
    synchronized (lock) {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Locking {}", ClassLayout.parseInstance(lock).toPrintable());
    }
    log.info("After lock {}", ClassLayout.parseInstance(lock).toPrintable());
}
Copy the code

The log is as follows:

Before lock 0x0000000000000005 (BiASable; Age: 0) Lock 0x000001B16E421005 (Biased: 0x000000006C5B9084; epoch: 0; Age: 0) Locked 0x000001B16E421005 (Biased: 0x000000006C5B9084; epoch: 0; Age: 0) Before locking 0x000001B16E421005 (Biased: 0x000000006C5B9084; epoch: 0; Age: 0) Before locking 0x000001B16E421005 (Biased: 0x000000006C5B9084; epoch: 0; Age: 0) Locked in 0x000001B10B4F0fBA (Fat Lock: 0x000001b10b4F0fba) Locked after 0x000001B10b4F0fba (Fat lock: 0x000001B10B4F0Fba) Locked in 0x000001B10B4F0FBA (Fat Lock: 0x000001B10B4F0fba) Locked after 0x000001B10B4F0fba (Fat Lock: 0x000001b10b4f0fba)Copy the code

As you can see from the log, show bias, then heavyweight locks, and finally not lock free