This is the 9th day of my participation in the August Wen Challenge.More challenges in August

Biased locking principle

In practical scenario, if a synchronized block (or methods) without multiple threads to competition, and always by the same thread re-entrant acquiring a lock for many times, if every time and blocked threads, wake up the CPU from user mode to kernel mode, then to the CPU is a waste of resources, in order to solve this kind of problem, introduced the concept of biased locking.

The core principle of biased locking is: If a thread without thread contention obtains the lock, then the lock will enter the biased state. At this time, the structure of the Mark Word changes to the biased lock structure. The lock bit of the lock object is changed to 01, and the biASED_lock bit is changed to 1. The ID of the thread is then recorded in the Mark Word of the lock object (done using the CAS operation). In the future, when the thread acquires the lock, it can directly enter the synchronization block by judging the thread ID and flag bit, without the need for CAS operation, which saves a lot of operations related to the lock application, and thus the performance of the provider.

Eliminating synchronization primitives in the case of no contention can further improve program performance. In the case of no lock contention, biased locking has a good optimization effect. However, as soon as a second thread is competing for the lock, the bias mode ends and the lightweight lock enters the state.

Biased locking demo

For information about printing object layouts using JOL, see Viewing Object Layouts using JOL

  1. The demo class

This class has an integer variable I and a synchronous method that performs +1 on variable I every time incr is called. And print the layout information for the object

public class Foo {

    private int i;

    public synchronized void incr(a){
        i++;
        System.out.println(Thread.currentThread().getName()+"-"+ ClassLayout.parseInstance(this).toPrintable()); }}Copy the code
  1. A Runnable implementation class

The run method inside calls the INCr method every time.

public class LockTest implements Runnable {

    private final Foo foo;

    LockTest(Foo foo) {
        this.foo = foo;
    }

    @Override
    public void run(a) {
        this.foo.incr(); }}Copy the code
  1. Biased locking demo

    3.1. Why sleep for five seconds?

    The JVM has a delay in enabling biased locking at startup. The JVM defaults to a bias lock delay of 4000ms, which explains why the demo case first takes 5 seconds to see the bias state of the object lock.

    If you don’t want to wait (sleep the thread in the code), you can disable bias lock delay directly by modifying the JVM’s startup options as follows:

    -XX:+UseBiasedLocking

    -XX:BiasedLockingStartupDelay=0

    3.2 5 seconds after sleep, create an instance of Foo, print the object header, execute incr, and print the object header again.

    @Test
    public void test(a) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        Foo foo = new Foo();
        System.err.println(ClassLayout.parseInstance(foo).toPrintable());
        foo.incr();
        System.err.println(ClassLayout.parseInstance(foo).toPrintable());

    }
Copy the code

Output 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)     0x00007ff84180f005 (biased: 0x0000001ffe10603c; 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

org.ywb.Foo object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007ff84180f005 (biased: 0x0000001ffe10603c; 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
Copy the code

Explanation:

  1. For the first print, you can see from the object structure that the biASED_lock state is enabled with a value of 1. Its lock status value is 01. The combination of lock and biASed_lock is 101, and converting to a hexadecimal value of exactly 5 indicates that the current Foo instance is in a biased lock state. However, the bias thread is null because no thread is using it yet.

  2. On the second print, you can see from the object structure that the bias lock has already been acquired by the thread of code executing synchronization. The MarkWord in the object header records the bias thread ID and the bias timestamp.

  3. By the third printing, the thread has finished executing the synchronization code, but you can see that the MarkWord of the object still points to the thread that was just bias, so that the next time the previous thread obtains the synchronization lock, it only needs to check whether it matches its own thread ID. It can also be seen that the bias thread does not unlock this operation.

Bias lock revocation and expansion

If there are multiple threads competing for a biased lock, if the object lock has been biased, and other threads find that the biased lock is not biased towards them, then there is a contention, they try to revoke the biased lock, and then expand to the lightweight lock.

Bias lock expansion

The specific process is as follows:

  1. Stop the thread that owns the lock at a safe point

Articles on safety points can be found at blog.csdn.net/weixin_3634…

  1. Iterate through the thread’s stack frame to check if there is a lock record. If there is a lock record, you need to clear the lock record to make it lock free, and repair the Mark Word that the lock record points to, clear its thread ID.

  2. Upgrade the current lock to a lightweight lock.

  3. Wake up the current thread.

Therefore, if there are two or more threads competing in some critical sections, biased locking can actually reduce performance. In this case, the default bias locking can be turned off when the JVM is started.

Undo bias lock condition

  1. Multiple threads compete for biased locks.

  2. After calling the hashcode() method of the bias lock object or the system.identityhashcode () method to calculate the hashcode of the object, the hashcode is placed into the Mark Word, the built-in lock becomes lock-free, and the bias lock is removed.

Bias lock expansion

If the bias lock is occupied, the second thread can see the bias state, indicating that there is already a contention on the object lock, since the bias lock will not be released voluntarily.

  1. The JVM checks that the owning thread that originally held the lock on the object is still alive, and if it hangs, it can make the object lock-free and then bias it back to the lock grab thread.

  2. If the JVM checks that the original thread is still alive, it further checks whether the call stack that owns the thread holds a biased lock through the lock record. If there is a lock record, it indicates that the original thread is still using the paranoid lock, lock contention occurs, the original biased lock is revoked, and the biased lock is inflated into a lightweight lock.