JUC01-synchronized及底层优化

一、synchronized如何解决线程安全问题

大家对于synchronized应该不陌生,在我们实际业务中经常需要书写同步代码块来保证线程安全性,而synchronized便是我们常用的一个加锁方式。那么问题来了,在高并发的情况下,synchronized是如何保证线程安全的呢?

Monitor

monitor又叫管程,是操作系统中用来保证共享资源正确使用的结构,java中也使用此机制来保证线程安全问题。当对一个对象执行synchronized操作时,在发生竞争的状态下,该对象的markword会存放操作系统的monitor地址,通过该monitor保证并发安全问题

monitor结构图
在这里插入图片描述

  1. 初始状态:monitor的owner为null
  2. t1:Thread-0、Thread-1依次获得了该锁并执行wait,释放锁并移到waitSet中
  3. t2:Thread-2获得该锁,owner指向Thread-2
  4. t3:此时Thread-2并没有释放锁,而Thread-3 4 5 都来到了临界区,由于此时owner不为null,因此它们进入阻塞队列
  5. t4:Thread-2执行notify(),随机从waitSet中唤醒一个线程,移至EntryList

二、轻量级锁

前文中讲到synchronized是使用monitor在多线程竞争的情况下保证并发安全的,但当我们业务中某个功能使用的人很少,并没有形成竞态时,使用monitor的方式过于“沉重”,因为monitor涉及到用户态->核心态的来回切换,jdk的开发人员替我们考虑到了这一点,当锁没有触发竞态条件时,使用轻量级锁的方式处理synchronized。

轻量级锁-过程

  1. 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的
    Mark Word
    在这里插入图片描述

  2. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存
    入锁记录
    在这里插入图片描述

  3. 如果 cas 失败,有两种情况

    • 3.1 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程

    • 3.2 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数
      在这里插入图片描述

      1. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重
        入计数减一
        在这里插入图片描述
      2. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象

        成功,则解锁成功
        失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有
竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁
在这里插入图片描述
在这里插入图片描述

自旋优化

当多线程竞争锁时,竞争失败的线程并不会马上进入阻塞队列,而是重试一定次数,若获取到锁则执行临界区代码,避免了上下文的切换,若是没有获取到则进入阻塞队列

三、偏向锁

偏向锁其实是一个非常鸡肋的东西,只有在确定只有一个线程会使用该锁的情况下,偏向锁才能体现它的作用。但凡有其它线程使用它,哪怕没有构成竞态,也会造成偏向锁的撤销或重偏向,造成性能的浪费,因此在jdk15以后废弃掉了它

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
但是一旦其它线程使用该锁,哪怕没有构成竞态,也会涉及偏向锁的撤销

偏向锁的三种撤销场景

场景一:调用锁对象的hashcode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被
撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

场景二:调用 wait/notify

场景三:其他线程使用该锁

将偏向锁升级为轻量锁

偏向锁的重偏向和批量撤销

重偏向
上文中说到,当有多个线程使用该锁时(不触发竞态),会将偏向锁升级为轻量锁,但当撤销的次数超过20次时,该锁会重偏向于当前使用此锁的线程

批量撤销
当一个锁的撤销偏向锁阈值超过 40 次后,jvm 会认为此类对象的锁不适合用偏向锁优化,会撤销该类对象的所有偏向锁,哪怕是新new的也不使用偏向锁

偏向锁的竞争问题

假设在使用偏向锁的情况下出现如下状况:
t1: Thread-1获得该锁,发现该锁是偏向锁,且处于匿名偏向锁状态(可偏向未锁定),将markWord中的0替换成线程id,执行同步代码块
t2: Thread-2也要获得该锁,发现该锁是Thread-1的偏向锁,开始进行偏向锁撤销,偏向锁的撤销需要等待全局安全点(safe point,代表了一个状态,在该状态下所有线程都是暂停的),暂停持有偏向锁的线程,检查持有偏向锁的线程状态(遍历当前JVM的所有线程,如果能找到,则说明偏向的线程还存活),如果线程还存活,则检查线程是否在执行同步代码块中的代码,如果是,则升级为轻量级锁,进行CAS竞争锁;
如果持有偏向锁的线程未存活,或者持有偏向锁的线程未在执行同步代码块中的代码,则进行校验是否允许重偏向,如果不允许重偏向,则撤销偏向锁,将Mark Word设置为无锁状态(未锁定不可偏向状态),然后升级为轻量级锁,进行CAS竞争锁;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值