Java锁的升级策略 偏向锁 轻量级锁 重量级锁

作者:laozhang 时间:2022-11-07 00:50:17 

这三种锁是指锁的状态,并且是专门针对Synchronized关键字。JDK 1.6 为了减少"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是通过对象头的Mark Word来进行标记的:

Java锁的升级策略 偏向锁 轻量级锁 重量级锁

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,是为了提高获得锁和释放锁的效率

重量级锁:依赖于底层操作系统的Mutex Lock,线程会被阻塞住

缺点:加锁和解锁需要从用户态切换到内核态,性能消耗较大

轻量级锁:基于重量级锁进行了优化(避免上下文切换,提高了性能),它假设多线程竞争是互相错开的,不会发生线程阻塞,呢么上下文切换就是多余的

第一个特点:采用了CAS操作加锁和解锁,由于轻量级锁的锁记录(Lock Record)是存放在对象头和线程空间里的,因此加锁和解锁不需要上下文切换,性能消耗较小

第二个特点:一旦发生多线程竞争,首先基于“自旋锁”思想,自旋CPU循环等待一段时间,不会发生上下文切换,如果还是无法获得锁,就将锁升级为重量级锁

偏向锁:基于轻量级锁进行了优化(减少多次的加锁和解锁,提高了性能),它假设整个过程只有一个线程获得锁,呢么多次的加锁和解锁就是多余的

特点:在第一次获得锁之后不会释放锁,它会一直持有锁,后续进入锁时只需检查一下锁状态和偏向线程ID是否为自己,从而省去了多次的加锁和解锁

1.偏向锁

获取锁:

检测对象头的Mark Word是否为可偏向状态(即是否为偏向锁1,锁标志位是否为01),如果不是,尝试竞争锁:尝试CAS操作将Mark Word的线程ID设置为当前线程ID,以表示线程获得锁,如果失败说明锁已被占用

若为可偏向状态,则检查线程ID是否为当前线程ID,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用

如果锁已被占用,只能撤销偏向锁为无锁状态或轻量级锁

释放锁:(偏向锁使用了一种等到竞争出现才释放锁的机制,线程是不会主动释放偏向锁的,只有当其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁)

偏向锁的撤销需要等待全局安全点(在这个时间点没有正在执行的字节码),暂停拥有偏向锁的线程,检查持有偏向锁的线程是否还活着

如果线程挂了,则将对象头设置成无锁状态;如果线程仍然活着,则将对象头设置为轻量级锁(锁的升级),最终轻量级锁一定会被释放

2.轻量级锁

获取锁:

检测对象头的Mark Word是否为轻量级锁(锁标志位为00),如果不是,尝试竞争锁:JVM首先在当前线程的栈帧中建立一个锁记录(Lock Record),用于备份存储对象头的Mark Word(官方把这份拷贝加了一个Displaced前缀,称为Displaced Mark Word),然后JVM尝试CAS操作将Mark Word更新为指向Lock Record的指针,以表示线程获得锁,如果失败说明锁已被占用

若为轻量级锁,判断对象头的Mark Word是否指向当前线程的栈帧的Lock Record,如果是则表示当前线程已经持有锁(锁的可重入),否则说明锁已被占用

如果锁已被占用,当前线程便尝试自旋CPU来获取锁,自旋一定次数后轻量级锁会膨胀为重量级锁(锁标志位变成10),线程进入阻塞

释放锁:

尝试CAS操作将Displaced Mark Word中替换回对象头,如果成功,说明轻量级锁释放成功

如果CAS操作失败,说明存在锁竞争,锁已经膨胀成重量级锁,需要在释放锁的同时唤醒那些被挂起的线程

3.重量级锁

重量级锁依赖于底层操作系统的Mutex Lock,所有线程都会被阻塞住,线程之间的切换需要从用户态到内核态,切换成本非常高。

总结:锁的优缺点对比

优点缺点适用场景
偏向锁(Biased Lock)加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销适用于只有一个线程访问
轻量级锁(Lightweight Lock)竞争的线程不会阻塞,提高了程序的响应速度对于得不到锁的线程,自旋会消耗CPU追求响应时间,或者要求临界区简短,自旋不会占用CPU过久
重量级锁(Heavyweight Lock)线程竞争不使用自旋,不会消耗CPU资源线程阻塞,响应时间缓慢追求吞吐量
标签:Java锁
0
投稿

猜你喜欢

  • c#读写App.config,ConfigurationManager.AppSettings 不生效的解决方法

    2021-10-07 22:34:42
  • Android-SPI学习笔记

    2022-05-15 17:35:33
  • java实现登录窗口

    2023-11-24 18:09:31
  • c# RSA非对称加解密及XML&PEM格式互换方案

    2022-07-10 12:19:31
  • Java 多线程同步 锁机制与synchronized深入解析

    2023-12-20 17:55:50
  • SpringBoot实现阿里云短信发送的示例代码

    2023-05-15 21:08:54
  • Java 常见的几种内存溢出异常的原因及解决

    2023-02-21 16:44:50
  • 浅谈Springboot之于Spring的优势

    2022-01-08 02:35:29
  • 一篇文章带你深入了解Java线程池

    2021-11-22 21:59:42
  • 使用javafx更新UI的方法

    2023-05-02 17:32:30
  • C#仪器数据文件解析Excel文件的方法浅析(xls、xlsx)

    2023-09-18 01:40:57
  • Android ToolBar整合实例使用方法详解

    2023-04-05 07:27:53
  • android采用FFmpeg实现音视频合成与分离

    2022-03-05 09:18:16
  • RxJava+Retrofit实现网络请求封装的方法

    2023-08-13 19:39:13
  • 在c#中使用servicestackredis操作redis的实例代码

    2022-06-23 14:28:48
  • Java TreeSet实现学生按年龄大小和姓名排序的方法示例

    2023-01-09 15:25:18
  • Android进阶Handler应用线上卡顿监控详解

    2022-12-21 11:31:00
  • Android硬件解码组件MediaCodec使用教程

    2023-03-14 01:35:36
  • Flutter基于Dart Unwrapping Multiple Optional小技巧

    2023-07-05 11:44:19
  • java使用集合实现通讯录功能

    2023-01-30 21:27:15
  • asp之家 软件编程 m.aspxhome.com