尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java的锁机制

Java的锁机制 一.Java对象头Synchronized用的锁是存储在Java对象头的所以理解Java对象头的存储结构和存储数据的类型有助于对锁的理解Java对象头中主要存储三类数据:第一类叫做MarkWord主要存储对象的hashcode,分代年龄锁信息等运行数据;第二类是Class Pointer指向方法区中该class的对象JVM通过此字段来判断当前对象是哪个类的实例;第三类数组的长度就是如果当前对象是数组的话才会有。三类中我们这里重点关注第一类MarkWord是我们理解锁的核心锁状态25bit4bit1bit是否为偏向锁2bit锁标志位无锁状态对象的hashcode对象分代年龄001程序运行期间MarkWord存储的信息会随着锁标志位的变化而变化可能会变化为以下四种状态之一;锁状态25bit4bit偏向锁标志1bit锁标志位2bit偏向锁23位线程ID2位Epoch✔101轻量级锁指向栈中锁记录的指针00重量级锁指向互斥量重量级锁的指针10GC标记空11会发现无锁、偏向锁的锁标志位”是一样的即都是01这是因为无锁、偏向锁是靠字段“是否是偏向锁”来区分的0代表没有启用偏向锁1代表启用偏向锁二.锁的升级与对比那么我们现在知道了锁有四种状态分别是:无锁偏向锁轻量级锁重量级锁。这几种锁的状态会随着并发竞争的情况逐渐升级锁只能升级不能降级(也就是说轻量级锁不能变成偏向锁)。2.1 偏向锁在大多实际环境下锁不仅不存在多线程竟争而且总是由同一个线程多次获取那么在同一个线程反复获取所释放锁中其中并没有锁的竟争那么这样看上去多次的获取锁和释放锁带来了很多不必要的性能开销和上下文切换。所以引入了偏向锁来处理这种情况。通过你会发现无锁、偏向锁的锁标志位”是一样的即都是01这是因为无锁、偏向锁是靠字段“是否是偏向锁”来区分的0代表没有启用偏向锁1代表启用偏向锁可以通过JVM参数(XX:UseBiasedLockingtrue 默认)控制。并且启动偏向锁还有延迟(默认4秒)可以通过JVM参数(XX:BiasedLockingStartupDelay0)来关闭延迟.偏向锁加锁:当一个线程A访问同步块并获取锁时会在对象头存储锁偏向的线程ID以后该线程在进入和退出 同步块时不需要进行CAS操作来加锁和解锁只需简单地测试一下对象头的MarkWord里是否存储着指向当前线程A的偏向锁。如果测试成功表示线程已经获得了锁。如果测试失败则需要再测试-下MarkWord中偏向锁的标识是否设置成1(表示当前是偏向锁):如果设置了则尝试使用CAS将对象头的偏向锁指向当前线程。如果没有设置则使用CAS竞争锁(即轻量级锁)假设有个同步块对象头就像是一个“VIP专座”。正常命中的情况测试成功线程 A 之前坐过这个 VIP 座对象头上刻着 “线程 A 的名字”。现在线程 A 又来了它看了一眼对象头“哟刻着我的名字呢”——测试成功。线程 A 直接进去干活连 CAS 都不用做效率极高。测试失败的情况重点刚才 线程 A 坐过对象头上刻的是 “线程 A 的名字”。这时线程 B 来了。线程 B 也去看对象头结果发现上面刻的是 “线程 A”不是自己。测试失败因为期望看到自己的名字结果看到了别人的名字。测试失败后线程 B 会直接放弃吗不会。接下来它会按照上述描述的那段话开始走“排查和抢夺”流程步骤一检查偏向标识是不是 1线程 B 会看一眼对象的标志位这个锁到底是不是偏向锁如果不是直接使用轻量级锁的 CAS 竞争。如果是的说明它确实是个偏向锁线程 B 会想“虽然上面写着 A 的名字但说不定 A 已经下班了呢”步骤二尝试用 CAS 把名字改成自己的线程 B 尝试用CAS比较并交换硬去修改对象头把“线程 A”的名字覆盖成“线程 B”。这里会出现两种结果CAS 成功说明线程 A 已经退出同步块了虽然对象头没来得及擦掉 A 的名字。这时线程 B 成功把名字改成了自己B 成功获取了偏向锁CAS 失败说明线程 A 还在同步块里干活呢根本不让位。这就产生了真正的线程竞争。步骤三锁升级轻量级锁因为发生了竞争CAS 失败JVM 发现单一线程的偏向已经不够用了于是触发偏向锁撤销把锁升级为轻量级锁。在轻量级锁阶段线程 B 会通过自旋不断循环尝试继续去竞争锁而不是直接失败报错。例子:当大呆需要上WC时只有它自已要上WC此时并没有其它的人需要上。WC那么这时这个WC可以直接给大呆使用并且大呆把可以标识自已身份的ID贴到门上表示此时大呆占用了这个WC。偏向锁撤销:还是用上面这个图来解释此时当前的WC被大呆所占用这时二呆来了也要使用WC。这时先等大呆解决完(执行完这次任务)大呆和二呆就要通过CAS的方式来抢占WC。因为此时锁的状态是偏向锁的状态二呆来了也要使用WC(这时有两个人同时要使用WC这时就要将偏向锁升级成轻量级锁)在升级轻量锁之前首先需要将WC上的标识大呆身份的ID撕下来(这一步叫做偏向锁的撤销)。2.2 轻量级锁上面锁被撤销后升级为了轻量级锁轻量级锁状态下两个人需要通过过自旋CAS的方式两个人来抢锁。当其中一个线程抢锁成功后会将LR贴到WC的门上表示WC当前被某个线程占用然后另一个没有抢到锁的线程就一直自旋获取锁。自旋的意思是占用CPU来反复尝试获取锁直到获取成功LR是Lock Record锁记录LR的锁记录中存储的是对象的MarkWord的备份即拷贝进入的而两个线程竞争的过程就是通过CAS的方式将对象本来的MarkWord位置存储的信息替换为指向自己LR记录的指针。谁替换成功了谁就获得了锁例如A成功了。那没有获取到锁的线程B就再自旋一段时间(自旋的原因是因为B认为A很快就能执行完我就在门口等一下也就是B认为竞争没有那么激烈)。当自旋-段时间后如果还没有获得锁那B就只能将锁修改为重量级锁了然后所有竞争锁的线程进入阻塞状态等待A执行完之后唤醒。2.3 重量级锁重量级锁线程加锁失败会进入阻塞状态等待前驱获得线程的锁执行完之后唤醒。总结:锁类型优点缺点适用场景偏向锁加锁和解锁不需要额外的消耗和执行非同步方法相比仅存在纳秒级的差距如果线程间存在锁竞争会带来额外的锁撤销的消耗适用于只有一个线程访问同步块的场景轻量级锁竞争的线程不会阻塞提高程序的响应速度如果始终得不到锁竞争的线程使用自旋会消耗CPU追求响应时间同步块执行速度非常快重量级锁线程竞争不使用自旋不会消耗CPU线程阻塞响应时间缓慢追求吞吐量同步块执行速度较慢注意偏向锁和重量级锁并没有使用monitor注: 本笔记为自己学习自存所用并未其它商业用途如有侵权即删.
返回列表