文章目录
-
前言
-
一、偏向锁
-
-
2.1 对象头
-
2.2 偏向锁的获取
-
2.3 偏向锁的关闭
-
1. 什么是偏向锁
-
2. 偏向锁的实现原理
-
-
二、轻量级锁
-
-
1. 什么是轻量级锁
-
2. 轻量级锁的获取
-
3. 轻量级锁的解锁
-
三、重量级锁
-
-
1. 什么是重量级锁
-
2. 重量级锁的获取
-
四、锁的升级与特点
-
-
1. 锁的标志位
-
2. 锁的对比
-
1. 锁的升级
-
2. 锁的特点
-
前言
最近整理了很多关于多线程的理论概念与底层实现,这里我尽量的使用图文结合的方式方便大家理解,如果有不清楚的地方也欢迎大家留言,我基本每天都在,可以在看到的第一时间回复。
一、偏向锁
1. 什么是偏向锁
偏向锁是JDK1.6提出的用与当前同步代码不存在竞争的情况下使用的。Hotspot虚拟机的设计者发现大多数情况下,锁对象不仅不存在多个对象之间的竞争,反而总是由同一个线程多次获得,为了降低线程获得锁的代价而引入了偏向锁。偏向锁是指当一个锁对象刚刚被创建的时候,它会偏向于第一个访问它的线程。此时线程修改对象头中关于偏向锁的标志位获取偏向锁。此后若不存在线程竞争,该偏向线程再次访问锁对象时不会触发同步。 偏向锁通过消除无资源竞争下的同步源语来增加程序的运行效率。
2. 偏向锁的实现原理
2.1 对象头
在讲偏向锁的实现原理前我们首先要了解一下什么是对象头:
HotSport虚拟机为了提高针对不同实例对象的运行效率,增强面向对象的功能,将一个对象分为了三部分:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。
其中对象头又分为三个部分:
markword
用于存储对象自身运行时数据,如哈希码(hashcode),GC分代年龄,锁状态标记,线程持有的锁,偏向线程ID,偏向时间戳等,根据32/64位虚拟机不同,其大小常分为32bit或64bit。klass
其主要用来表述对象指向它的类元数据指针,虚拟机通过这个指针来判断当前对象是哪个类的实例。数组长度
如果一个对象是数组,那么对象头还需要存储数组长度
偏向锁的实现主要应用了对象头中的MarkWord,这里我们看下对象头(32位虚拟机)中的markword的存储结构如何
2.2 偏向锁的获取
这里还是按照惯例,先上一个关于偏向锁获取的流程图:
下面我们就可以看图写小作文了:
当一个线程访问同步代码块的时候,会判断此时的锁对象是否偏向其他线程:
如果没有偏向其他线程的话则会在锁对象的对象头和栈帧中存储偏向锁偏向的线程ID,以后进入和退出该同步块时不需要再触发CAS操作来加锁和解锁。
如果此时偏向其他线程,则需要通过CAS操作竞争锁,如果竞争成功则修改偏向锁偏向的线程ID,如果竞争失败,则表示线程间存在竞争,当到达全局安全点会将获得偏向锁的线程挂起,偏向锁升级为轻量级锁。(撤销偏向锁会引起STW stop the world)
全局安全点:此时没有任何正在执行的字节码
2.3 偏向锁的关闭
偏向锁在JDK 1.6 和JDK 1.7 中是默认开启的,但是他在应用程序启动几秒后才会被加载,如果有必要的话可以通过修改JVM参数来关闭延迟: -XX:BiasedLockingStartupDelay = 0 。当然,如果你能够预估到线程间的竞争比较激烈也可以通过参数直接关闭偏向锁的使用:-XX:UserBiasedLocking = false ,这样程序默认进入轻量级锁状态。
二、轻量级锁
1. 什么是轻量级锁
JDK 1.6 之后为了减少获取锁和释放锁带来的性能开销,引入了轻量级锁。轻量级锁应用于线程之间存在竞争但竞争不激烈的情况,其主要通过线程自旋代替阻塞来提升程序的响应速度。
2. 轻量级锁的获取
轻量级锁是由偏向锁升级而成,当然如果程序禁用了偏向锁,当线程第一次访问同步代码块的时候默认会获得轻量级锁。这里针对轻量级锁的获取流程我们还是通过一个简单的流程图说明下:
以下描述出自《Java并发编程的艺术 》
线程在执行同步代码块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间(Lock Record)并将对象头中的Markword拷贝到lockRecord中,官方称为Displaced Mark Word(替换标志位)。然后线程将尝试通过CAS将对象头中的Mark Word替换为指向所记录的指针。如果成功,则当前线程获得锁,如果失败表示有其他线程在竞争锁,当前线程自旋等待下次获取锁的机会。
3. 轻量级锁的解锁
轻量级锁解锁时,会使用原子性的CAS操作将Displaced Mark Word替换到对象头,如果成功则表示没有竞争,如果失败则表示当前锁存在竞争,此时轻量级锁会膨胀为重量级锁。
三、重量级锁
1. 什么是重量级锁
在Java中提供了一种原子性内置锁synchronized,他依赖于将内置锁抽象为moniter(监视器锁)实现的。在JDK1.6之前使用synchronized的代价是非常庞大的,它依赖于直接对操作系统中的互斥量进行操作,其实现锁功能的代价是挂起和唤醒线程都需要通过操作系统内核完成,十分消耗性能,因此这种锁也被称为重量级锁。
2. 重量级锁的获取
重量级锁通常为轻量级锁升级而来,一旦升级为重量级锁,所有竞争锁的线程需要同步阻塞等待竞争锁。这里还是按照惯例画一个简单的流程图:
四、锁的升级与特点
JDK1.6设计者针对synchronized做了一系列优化,包括引入偏向锁,自适应自旋锁,轻量级锁解决不同线程竞争程度下synchronized的加锁策略。现在synchronized的性能已经可以和reentrantlock有一拼,甚至java官方都在推荐使用synchronized关键字作为同步策略。那么很多人会好奇,不同的锁是如何进行转化的,并且不同的锁的升级过程如何?今天的最后我们就来总结一下锁的相关特点。
1. 锁的升级
锁的升级过程还是通过一个简单的流程图说明下:
看到自己画的图,滋滋滋我是真的厉害,哈哈哈哈哈哈哈哈哈。
上图基本已经概括了锁升级的完整过程,其中升级条件也已经注明,如果有不明白的也可以在评论区留言或者私信我~
2. 锁的特点
1. 锁的标志位
在锁的升级过程中,提到了一个锁的标志位的概念,锁的标志位其实存储在对象头的mark word中,这里可以参考下《Java并发编程艺术》中对于32位虚拟机的对象头标志位状态变化表(这个表实在是懒得画了,直接粘的书中的表):
2. 锁的对比
这里针对锁的优缺点做个简单的阐述:
偏向锁
优点: 加锁和释放锁不需要额外的操作,和执行非同步方法的差距仅在纳秒级
缺点: 偏向锁升级为轻量级锁时会引起stw造成卡顿
适用场景:只有一个线程访问同步块
轻量级锁:
优点: 不会造成线程堵塞,无需等待线程的休眠和唤醒,提高了程序的响应速度。
缺点: 过多的自旋会带来额外的cpu消耗
适用场景:追求响应时间,同步块执行速度非常快
重量级锁:
优点:线程竞争无需自旋,不会消耗CPU
缺点:线程阻塞,响应时间慢
适用场景:追求吞吐量,同步块执行较长
好了,锁的升级与对比想过的知识点就到这里了,有收获的小伙伴希望可以一键三连给博主个鼓励!
祝三连的小伙伴可以升职加薪迎娶白富美!!!!
原文始发于微信公众号(青莲明月):晓龙吊打面试官系列:锁的升级与对比(java中的偏向锁、轻量级锁、重量级锁)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/107809.html