Bootstrap

Synchronized锁升级详细过程

最近翻看leaf源码时,同事提出了一段关于synchronized的idea上报黄是啥情况 ,正好关于synchronized的内容也很久复习过,趁着这个机会讲解一下,在网上翻看了大量文章,发现很

多文章都是有点问题。。本文经过大量实践详细讲解锁升级的一个过程

如有不对,欢迎指正。

本文适合对synchronized有一定了解的同学阅读(至少要知道锁的几个级别)

定义

  1. 大家都知道锁分为无锁->偏向锁->轻量级锁->重量级锁

这也是锁的一个升级过程,但是实际上锁的升级并不是按步就班的,可以直接从无锁升级为重量级

  1. 很多文章写了锁不可以降级,其实这句话有点误导,锁是不可以降级。但是可以变更为无锁

对象头中的mark word

这个图还是很有用的,很多地方要对着这个图来看

锁的详细升级过程

我们直接从偏向锁讲起,首先配置启动参数-XX:BiasedLockingStartupDelay=0 设置偏向锁启动延时0秒(默认好像是4s)

分为以下情况

  1. 单线程没有竞争

线程获取锁了直接就是偏向锁(如果在偏向锁没启动时,则是轻量级锁)

代码:

import lombok.SneakyThrows;
import org.openjdk.jol.info.ClassLayout;

/**
 * @author TanJ
 * @date 2023/1/6 16:42
 */
public class SyncTest {

    @SneakyThrows
    public static void main(String[] args) {
        Object lock = new Object();
        // 打印初始状态
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        synchronized (lock) {
            // 打印偏向锁状态
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
          //  TimeUnit.SECONDS.sleep(2);
        }
    }
}

结果

这里不管线程是否释放锁,锁状态都是偏向锁(就算已经释放也是)

  1. 多线程没有竞争

多线程没有竞争的情况会比较特殊一点,可能会升级为轻量级,也可能还是偏向锁,也有可能会偏向优化

保持偏向锁:多线程没有竞争,前一个线程已经执行完毕

代码:

import lombok.SneakyThrows;
import org.openjdk.jol.info.ClassLayout;

/**
 * @author TanJ
 * @date 2023/1/6 16:42
 */
public class SyncTest {
    static final Object yesLock = new Object();
    
    @SneakyThrows

    public static void main(String[] args) {
        Runnable runnable = () -> {
            synchronized (yesLock) {
                System.out.println("线程[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId() + "]" +
                        ":对象布局:" + System.currentTimeMillis() + ClassLayout.parseInstance(yesLock).toPrintable());
            }
      
        };

        Thread t0 = new Thread(runnable);
        Thread t1 = new Thread(runnable);
        t0.start();
        t0.join();
  
        System.out.println("t0 线程执行完毕 ");
        t1.start();
    
        System.out.println("获取二次并且释放之后,此时的对象布局:" + ClassLayout.parseInstance(yesLock).toPrintable());

    }
}


可以看到就算释放了锁。最后都是偏向锁,并且偏向的是第一个线程(并不总是第一个线程,其实当偏向升级或者撤销超过一定阈值,可能会重新指定为别的线程ID)

轻量级锁:没有竞争锁,前一个线程未执行完毕,但是已经释放了锁。

代码:

 public static void main(String[] args) throws InterruptedException {

       
        // 打印初始状态
        System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());

        synchronized (yesLock) {
            // 打印偏向锁状态
            //  System.out.println("hash code"+lock.hashCode());
            System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
            TimeUnit.SECONDS.sleep(2);
        }

        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            synchronized (yesLock){
                // 打印轻量级锁,因为上次持有的线程为main线程,正在运行中,但是已经释放了锁
                System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        // 打印无锁
        System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
    }

结果:

这边要注意的是,轻量级锁进行释放锁不会变成偏向锁,只会设置为无锁状态

  1. 多线程有竞争

这没啥好说的,直接就是重量级锁

补充

  1. 调用锁对象的hase code方法,如果未持有锁,会变成无锁状态(因为hashCode与其它线程ID都在一块存储区域) 如果持有锁,会直接变成重量级锁,因为已经存在锁了,无法释放锁,且hashcode可以存放在Monitor上同理,调用wait方法也会直接升级为重量级锁 这个是因为只有在monitor上才存在waitset

补一张monitor的图吧

2...其它想到了在补充吧,暂时就这些

;