描述一下锁的四种状态及升级过程?

发布时间 2023-03-22 19:13:52作者: 程序员Forlan
 

1、锁的四种状态

无锁、偏向锁、轻量级锁、重量级锁

2、Java对象头描述

以下为32位对象头描述
在这里插入图片描述以下为64位对象头描述
在这里插入图片描述

3、锁的升级过程(Synchronized加锁/膨胀流程)

1)简单过程如下图

在这里插入图片描述

2)详细过程

当线程访问同步代码块时,首先判断当前锁状态是否为可偏向状态(对象头中偏向锁=1,锁标志=01)
在JDK1.6以上默认开启,开启后程序启动几秒后才会被激活

(1)偏向锁

如果是可偏向状态,检查MarkWord存储的是否是当前线程ID

  • 是,获得偏向锁,执行同步代码块
  • 不是,CAS操作竞争锁,替换线程ID
    • 替换成功,MarkWord的线程ID设置为当前线程ID(线程复用),执行同步代码块
    • 替换失败,锁撤销,升级为轻量级锁
      同一类对象多次撤销升级达到阈值20,则偏向锁认为,后面的锁需要重新偏向新的线程(批量重偏向)
      如果阈值达到40次,则偏向锁认为偏向锁撤销过于频繁,后面直接使用轻量级锁

(2)轻量级锁

升级为轻量级锁的情况

  • 如果不是可偏向状态,直接升级为轻量级锁
  • 偏向锁撤销次数过多

加锁时,会在当前线程栈帧中划出一块空间,作为该锁记录,并且将锁对象MarkWord复制到该锁记录中,CAS操作将MarkWord更新为该锁记录的指针,锁记录中的owner指针指向对象头的MarkWord。

  • 更新成功,MarkWord锁标志位为00,表示轻量级锁状态
  • 更新失败,检查锁对象MarkWord是否指向当前线程栈帧中的锁记录
    • 是,表示锁重入,在当前线程栈帧中锁记录+1
    • 否,自旋等待(默认10次),等待次数达到阈值,升级为重量级锁

(3)重量级锁

升级为重量级锁的情况

  • 竞争加剧,CAS自旋到一定次数升级为重量级锁
  • 自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋Adapative Self Spinning,JVM自己控制

获取锁成功,进入EntryList(获取锁的缓冲区、入口)

  • 在调用wait方法后,会进入等待唤醒队列(WaitSet)等待
  • 在调用notify方法后,则可能进入EntryList
    获取锁失败,进入一个等待拿锁队列(cxq)等待

具体重量级锁加锁过程:
1、分配⼀个ObjectMonitor对象,把MarkWord锁标志置为‘10’,然后MarkWord存储指向ObjectMonitor对象的指针。ObjectMonitor对象有两个队列和⼀个指针,每个需要获取锁的线程都包装成ObjectWaiter对象
2、多个线程同时执行同⼀段同步代码时,ObjectWaiter先进⼊EntryList队列,当某个线程获取到对象的monitor以后进⼊Owner区域,并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count+1

说明:
monitor:每个Java对象都有一把锁,称为内部锁或monitor锁
owner,指向的是当前获得线程的地址,用来判断当前锁是被哪个线程持有

4、拓展

1)synchronized效率低?

用户态:偏向锁、轻量级锁
内核态:重量级锁

首先来了解下synchronized重量级锁实现原理?
  通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高。
  在JDK6以前,只有重量级锁,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间。
  在JDK6中,为了提高性能,引入了偏向锁和轻量级锁。

2)为什么要有偏向锁?

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了减少线程获得锁的代价,所以引入了偏向锁

3)为什么要有重量级锁?

自旋锁消耗CPU资源,重量级锁有等待队列,不会消耗CPU资源

4)偏向锁是否一定比自旋锁效率高?

不一定,在多线程竞争情况下,偏向锁会涉及锁撤销,这时候应该直接使用自旋锁

5)锁重入

重入次数必须记录,才能知道要解锁几次

  • 轻量级锁,记录在线程栈,每插入一次,LockRecord+1
  • 重量级锁,记录在ObjectMonitor字段上

6)Hopspot对象头就是MarkWord?

不是的,Hopspot对象头主要包括两部分数据:MarkWord(标记字段) 和 classPointer(类指针)

7)锁可以降级?

不行的,是一个不可逆的过程,主要是为了提高获得锁和释放锁的效率

8)锁对比,适用场景?

偏向锁:适用于一个线程,不会有锁消耗,锁撤销
轻量级锁:适用于多个线程竞争,但同步代码块执行快的情况下,因为自旋会消耗CPU
重量级锁:适用于多个线程竞争,但同步代码块执行慢的情况下,不消耗CPU,可以提高吞吐量