3

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

 1 year ago
source link: https://www.cnblogs.com/huozhonghun/p/17185298.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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,指向的是当前获得线程的地址,用来判断当前锁是被哪个线程持有

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,可以提高吞吐量


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK