Java synchronized偏向锁的核心原理详解
作者:小小茶花女 发布时间:2022-12-26 12:11:58
1. 偏向锁的核心原理
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。 Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
public class Main {
static final Object obj = new Object();
public static void main(String[] args) {
Thread thread = new Thread(()->{
m1();
});
thread.start();
}
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
//偏向状态
// 同步块 C
}
}
}
偏向锁的核心原理是:如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark Word的结构变为偏向锁结构,锁对象的锁标志位(lock)被改为01,偏向标志位(biased_lock)被改为1,然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完成)。以后该线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。
偏向锁的主要作用是消除无竞争情况下的同步原语,进一步提升程序性能,所以,在没有锁竞争的场合,偏向锁有很好的优化效果。但是,一旦有第二条线程需要竞争锁,那么偏向模式立即结束,进入轻量级锁的状态。
假如在大部分情况下同步块是没有竞争的,那么可以通过偏向来提高性能。即在无竞争时,之前获得锁的线程再次获得锁时会判断偏向锁的线程ID是否指向自己,如果是,那么该线程将不用再次获得锁,直接就可以进入同步块;如果未指向当前线程,当前线程就会采用CAS操作将Mark Word中的线程ID设置为当前线程ID,如果CAS操作成功,那么获取偏向锁成功,执行同步代码块,如果CAS操作失败,那么表示有竞争,抢锁线程被挂起,撤销占锁线程的偏向锁,然后将偏向锁膨胀为轻量级锁。
偏向锁的加锁过程为:新线程只需要判断内置锁对象的Mark Word中的线程ID是不是自己的ID,如果是就直接使用这个锁,而不使用CAS交换;如果不是,比如在第一次获得此锁时内置锁的线程ID为空,就使用CAS交换,新线程将自己的线程ID交换到内置锁的Mark Word中,如果交换成功,就加锁成功。
每执行一轮抢占,JVM内部都会比较内置锁的偏向线程ID与当前线程ID,如果匹配,就表明当前线程已经获得了偏向锁,当前线程可以快速进入临界区。所以,偏向锁的效率是非常高的。总之,偏向锁是针对一个线程而言的,线程获得锁之后就不会再有解锁等操作了,这样可以省略很多开销。
偏向锁的缺点:如果锁对象时常被多个线程竞争,偏向锁就是多余的,并且其撤销的过程会带来一些性能开销。
2. 偏向锁的撤销
假如有多个线程来竞争偏向锁,此对象锁已经有所偏向,其他的线程发现偏向锁并不是偏向自己,就说明存在了竞争,尝试撤销偏向锁(很可能引入安全点),然后膨胀到轻量级锁。
偏向锁撤销的开销花费还是挺大的,其大概过程如下:
(1) 在一个安全点停止拥有锁的线程。
(2) 遍历线程的栈帧,检查是否存在锁记录。如果存在锁记录,就需要清空锁记录,使其变成无锁状态,并修复锁记录指向的Mark Word,清除其线程ID。
(3) 将当前锁升级成轻量级锁。
(4) 唤醒当前线程。
所以,如果某些临界区存在两个及两个以上的线程竞争,那么偏向锁反而会降低性能。在这种情况下,可以在启动JVM时就把偏向锁的默认功能关闭。
撤销偏向锁的条件:
(1) 多个线程竞争偏向锁。
(2) 调用偏向锁对象的hashcode()方法或者System.identityHashCode()方法计算对象的HashCode之后,将哈希码放置到Mark Word中,内置锁变成无锁状态,偏向锁将被撤销。
3. 偏向锁的膨胀
如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表明在这个对象锁上已经存在竞争了。JVM检查原来持有该对象锁的占有线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进行重新偏向,偏向为抢锁线程。
如果JVM检查到原来的线程依然存活,就进一步检查占有线程的调用堆栈是否通过锁记录持有偏向锁。如果存在锁记录,就表明原来的线程还在使用偏向锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁。
4. 偏向锁的好处
经验表明,其实大部分情况下进入一个同步代码块的线程都是同一个线程。这也是JDK会引入偏向锁的原因。所以,总体来说,使用偏向锁带来的好处还是大于偏向锁撤销和膨胀所带来的代价。
来源:https://hengheng.blog.csdn.net/article/details/123178723


猜你喜欢
- 前言在项目开发中经常会用到配置文件,配置文件的存在解决了很大一份重复的工作。今天就分享四种在Springboot中获取配置文件的方式。注:前
- 本文实例讲述了JDBC使用游标实现分页查询的方法。分享给大家供大家参考,具体如下:/*** 一次只从数据库中查询最大maxCount条记录*
- 在Spring MVC中想要对每一个URL进行权限控制,不想手工整理这样会有遗漏,所以就动手写程序了。代码如下: /** &nb
- 一:简述如果我们想要生成一个随机数,通常会使用Random类。但是在并 * 况下Random生成随机数的性能并不是很理想,今天给大家介绍一下J
- 一、前言随着微服务治理手段越来越多,对开发者来说,不同微服务之间的接口调用是日常开发中非常普遍的事情,以springcloud生态体系的微服
- 一.代码实现1. “Activity_11\src\yan\activity_11\MainActivity.java”pack
- ReadWriteLock 和 ReentrantReadWriteLock介绍ReadWriteLock,顾名思义,是读写锁。它维护了一对
- 调取钉钉考勤接口的功能公司需要做一个钉钉考勤的页面,让我去写这个功能。结果却比我想象的要麻烦一些!具体是怎么个麻烦呢下面直入正题首先我们找到
- 在学习C#语言的时候,首先要学习控制台的应用程序,这样才能专注于语言的学习,减少学习的梯度,也有利于输出自己需要输出的内容。因此第一步学习C
- 本文实例讲述了Android编程创建桌面快捷方式的常用方法。分享给大家供大家参考,具体如下:Android在桌面上生成快捷方式有两种情况,一
- 本文实例讲述了Android6.0开发中屏幕旋转原理与流程。分享给大家供大家参考,具体如下:从Android 系统开发开始,这里写下Andr
- 读取文本文件中的内容此示例读取文本文件的内容以使用 System.IO.File 选件类的静态方法 ReadAllText 和 ReadAl
- 1、自定义消息转换器MessageConverter在WebMvcAutoConfiguration类中有一个方法configureMess
- 本文实例为大家分享了OpenCV实现人脸识别程序的具体代码,供大家参考,具体内容如下//Haar特征检测,人脸识别算法,是用xml作为训练后
- 一、什么是CharacterEncodingFilter官方解释如下是spring内置过滤器的一种,用来指定请求或者响应的编码格式。在web
- 本文为大家分享了Android实现带动画效果的可点击展开TextView 制作代码,效果图: 收起(默认)效果:点击展开后的效果:源码: 布
- 方法的返回值为什么要有带返回值的方法呢?调用处拿到方法的结果之后,才能根据结果进行下一步操作带返回值方法的定义和调用:如果在调用处,要根据方
- 背景项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对
- Android studio开发工具中,如何如何删除Android项目,下面是在Android studio 1.5正式版删除Android
- 背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。其中又分01背包和无限背包,这里主要