Synchronized锁升级之轻量级锁

1.什么是轻量级锁

线程之间存在锁的伪竞争行为,即同一时刻绝对不会存在两个线程申请获取锁,各个线程尽管都有使用锁的需求,但是是交替使用锁(轻量级锁情况下,线程还是不会发生堵塞)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCXy95tQ-1680189167196)(https://pl1bu9mnlb.feishu.cn/space/api/box/stream/download/asynccode/?code=Y2ViMmVkOGEzMWIxNGY0YjljYzE3NjFhOGRhMGI1ODdfWlhqcHVpQjhpc0NYREdEaWlQdEtzTFpXT1QyRGtjOUVfVG9rZW46Ym94Y25kVXpTR0dZMmNrdnBHTTBjelZFRlI5XzE2ODAxODkxNTY6MTY4MDE5Mjc1Nl9WNA)]

2.为什么引入轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的场景。因为阻塞线程需要CPU从用户态转到内核态,代价比较大,如果刚刚堵塞不久这个锁被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不堵塞这个线程,让他自旋等待锁的释放

3.轻量级锁的升级时机

  1. 关闭偏向锁功能
    1. 使用 -XX:-UseBiasedLocking参数关闭偏向锁,此时默认进入轻量级锁
  2. 多个线程竞争偏向锁
    1. 偏向锁状态下,由于别的线程尝试竞争偏向锁,并且CAS更新MarkWord中线程ID失败,此时发生【偏向锁 >> 轻量级锁】升级

4.轻量级锁演示案例

  1. case1
public class Main {
    public static void main(String[] args) {
        // -XX:UseBiasedLocking 关闭偏向锁功能,此时在有锁竞争的场景下 objLock起步就是轻量级锁
        Object objLock = new Object();
        synchronized (objLock){
            System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
        }
    }
}

在这里插入图片描述

  1. case2
public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread.sleep(5000);

        Object objLock = new Object();
        System.out.println(Thread.currentThread().getName() + " - " + ClassLayout.parseInstance(objLock).toPrintable());

        new Thread(() -> {
            synchronized (objLock){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName() + " - " + ClassLayout.parseInstance(objLock).toPrintable());
        },"T1").start();

        Thread.sleep(1000);

        new Thread(() ->{
            synchronized (objLock){
                System.out.println(Thread.currentThread().getName() + " - " + ClassLayout.parseInstance(objLock).toPrintable());
            }
        },"T2").start();
    }
}
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
main - java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

T1 - java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 38 41 cc (00000101 00111000 01000001 11001100) (-868141051)
      4     4        (object header)                           96 7f 00 00 (10010110 01111111 00000000 00000000) (32662)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

T2 - java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           78 e8 5f 90 (01111000 11101000 01011111 10010000) (-1872762760)
      4     4        (object header)                           96 7f 00 00 (10010110 01111111 00000000 00000000) (32662)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结合代码与输出结构进行分析

  1. 首先是在执行代码前对主线程(mian)进行了5s的阻塞,这样的目的是为了让偏向锁加载成功,到后面线程T1去获取锁对象的时候,此时的锁就是一个偏向锁
  2. 在T1线程的同步代码块中对T1线程进行了1s的阻塞,用来模拟线程持有锁,但是持有时间并不长
  3. 在创建T2线程前对主线程进行了1s的阻塞,这里是为了避免在同一时刻有超过一个线程对同一把锁的竞争,确保这里一定是T1先获取到锁(避免锁的膨胀导致锁最终变成重量级锁)
  4. 当创建T2线程获取锁的时候T1线程恰好使用完了锁,并释放了锁,此时T2线程会尝试通过CAS去修改Mark Word中的偏向线程ID,此时修改偏向线程ID失败,导致锁从偏向锁升级到轻量级锁

5.轻量级锁的原理

5.1.轻量级锁的加锁

  1. JVM会在当前线程的栈帧中创建一个名为锁记录(LockRecord)的空间,用于存储锁对象目前Mark Word的拷贝(官方称为 Displaced Mark Word)若一个线程获取锁发现是轻量级锁,它会将对象的Mark Word复制到栈帧中的LockRecord中(Displaced Mark Word里面)
  2. 线程尝试利用CAS操作将对象的Mark Work更新为指向LockRecord的指针,如果成功表示当前线程竞争到了锁,如则将锁的标志位改成00,执行同步操作
  3. 如果失败,表示Mark Work已经被替换成其他的线程锁记录,说明在于其他线程竞争锁,当前贤臣会尝试使用自旋来获取锁

5.2.轻量级锁的释放

轻量级所得释放也是通过CAS操作来进行的,当前线程使用CAS操作将Displaced Mark Word的内存复制回锁对象的Mark Word,如果CAS操作成功,则说明锁释放成功;如果CAS自旋多次还是替换失败的话,说明有其他线程尝试获取锁(同时有多个线程对锁产生了竞争),则需要将轻量级锁膨胀升级为重量级锁

6.轻量级锁的升级流程

暂时无法在飞书文档外展示此内容

7.轻量级锁的优缺点

  • 优点:在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗
  • 缺点:如果长时间自旋还没有竞争到锁,将会过度消耗CPU,即CPU空转

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值