十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
本文介绍偏向锁相关原理,并不限定于Java中的偏向锁,但是Java中偏向锁的实现也是相同的原理,本文主要是对参考文献( Quickly Reacquirable Locks )中偏向锁实现重点部分的翻译,加入了自己的理解,参考文献称偏向锁为可快速获取的锁(QRL,Quickly Reacquirable Locks)。如何快速获取将会在第2节介绍过相关数据结构之后介绍。偏向锁主要是为了提高绝大部分情况下不存在竞争、只有一个线程在尝试获取锁的场景,通过相关数据结构可以减少CAS操作的数量,提高应用性能。偏向锁在有多个线程竞争获取时,会变成(论文中称为revoke,撤销)普通的锁(或默认锁),在Java中则会变先变为轻量级锁。
成都创新互联公司服务项目包括邗江网站建设、邗江网站制作、邗江网页制作以及邗江网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,邗江网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到邗江省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
相对于一般的锁来说,偏向锁使用了两个额外的域(这里不能等同于Java类成员域,在Java中其实是使用对象头中的Mark Word保存的)。第一个域是用来记录锁当前状态的域,可能的状态包括 NEURAL , BIASED , REVOKING , DEFAULT 。
注意:撤销和释放(release)锁不同,这里的撤销(revoke)指的是从偏向锁退化为普通锁的过程。
初始状态下锁处于 NEURAL 状态,当该锁被第一次获取时,获取锁的线程会将锁状态变为 BIASED 。此时如果有另一个线程悔仿友尝试获取同一个锁,那么其最终会将锁状态变为 DEFAULT ,在变为 DEFAULT 状态之前该锁会根据撤销协议先处于一个暂时的中间状态 REVOKING 。当锁状态为 BIASED 时,状态中会有一个域用来记录当前持有锁的线程标识符(后文使用线程ID代替)。对于一个不可再偏向(non-rebiasable,表示一旦撤销之后不可再次回到偏向状态)的锁,则锁状态只能按照 NEURAL - BIASED - REVOKING - DEFAULT 的状态进行切换,不可回退。但是偏向锁也可实现为可重偏向的锁(rebiasable)。
实现偏向锁需要额外增加的第二个域是一个可以指示布尔类型的位(bit)即可,用来表示偏向锁当前持有者对锁是获取状态还是碧槐释放状态。有了这个标识,当偏向锁被获取并且没有撤销时,当前持有者线程的获取和释放操作只要简单的改变该域的状态即可。
注意,一个线程获取偏向锁首先需要成为偏向锁的持有者(holder),即将自己的线程ID通过CAS写入锁状态域中,然后再获取锁(acquire),持有并不一定获取了锁,持有之后必须改变上面布尔标识位才获取了锁,但是获取锁之前必须要先成为锁的持有者;释放(release)锁之后也需要有其他线程显示竞争之后当前持有者才会失去持有者身份,锁的持有者可以通过改变上面介绍的布尔位进行锁的获取和释放动作。
将锁从初始的 NEUTRAL 状态转变为 BIASED 状态可以仅通过一次CAS操作完成。这里使用CAS操作是必须的,因为需要避免多个线程同时尝试获取 NEUTRAL 状态锁并置其状态为 BIASED 。
现在我们介绍第1节概述中 快速获取 的含义,根据上面的数据结构介绍,当一个线程获取了偏向锁之后,会在锁中记录线程ID,锁中也会有一个标识位用来表示该锁目前是释放的还是被获取的。因此以后线程在获取和释放锁时,只要检测锁是否记录自己的线程ID即可,如果检测成功,表示线程已经是偏向锁的持有者,直接通过改变上述布尔类型标识位域进行获取和释放锁即可。如果测试失败(表示该锁目前持有者不是自己),则再测试一下锁当前状态,如果还是 BIASED 状态,则使用CAS竞争锁,如果CAS成功,则尝试使用CAS将锁状态中线程ID置为自己的线程ID,这样后续在获取和释放锁时就不用使用CAS操作了。上面测试过程没有使用CAS操作,因此提高了性能,实现了 快速获取 锁。
实现上面atomic-free(表示尽可能减少CAS这样的原子操作)偏向锁的难点就在于如何协调获取偏向锁和撤销偏向锁的过程。必须处理偏向锁获取和释放同时发生偏向锁撤销时的多线程竞争问题,可以通过使用CAS将锁的状态改为 REVOKING 来避免大瞎后一种竞争:通过CAS将锁状态改为 REVOKING 也可能会有多线线程同时进行,但是CAS保证只有一个线程会成功改变锁状态,称为真正的撤销者(revoker),其他的线程则尝试着获取默认锁(默认锁即偏向锁撤销完成之后变成 DEFAULT 状态的锁)。
下面介绍偏向锁操作过程(获取、释放、撤销等)的四种主要场景:
参考文献后面还介绍了可再偏向(rebiasable)锁的实现原理,即在合适的时机再次尝试将锁从 DEFALUT 状态置为 BIASED 状态,这里不再介绍,有兴趣可以看看参考文献。
Dave D., Villiam S.; Quickly Reacquirable Locks
给你整理了Java中的一早茄裤些锁:
公平锁/非公平锁
可重入陆简锁
独享锁/共享锁
互斥锁/读写锁
乐观锁/悲观锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁纳圆的特性,有的指锁的设计
《揭秘Java虚拟机:JVM设计原理与实现》是2017年电子工业出版社出版的图书,作者是封亚飞。
基本介绍 书名 :揭秘Java虚拟机:JVM设计原理与实现 作者 :封亚飞 ISBN :9787121315411 页数 :察辩中700 出版时间 :2017-06 开本 :16开 千 字 数 :942 内容简介,目录, 内容简介 《揭秘Java虚拟机:JVM设计原理与实现》从源码角度解读HotSpot的内部实现机制,本书主要包含三大部分——JVM数据结构设计与实现、执行引擎机制及记忆体分配模型。 数据结构部分包括Java位元组码档案格式、常量池解析、栏位解析、方法解析。每一部分都给出详细的源码实现分析,例如栏位解析一章,从源码层面详细分析了Java栏位重排、栏位继承等关键机制。再如方法解析一章,给出了Java多态特性在源码层面的实现方式。《揭秘Java虚拟机:JVM设计原理与实现》通过直接对原始码的分析,从根本上梳理和澄清Java领域中的关键概念和机制。 执行引擎部分包括Java方法调用机制、栈帧创建机制、指令集架构与解释器实现机制。这一话题是《败山揭秘Java虚拟机:JVM设计原理与实现》技术含量高的部分,需要读者具备一定的汇编基础。不过千万不要被“汇编”这个词给吓著,其实在作者看来,汇编相比于高级语言而言,语法非常简单,语义也十分清晰。执行引擎部分重点描述Java原始码如何转换为位元组码,又如何从位元组码转换为机器指令从而能够被物理CPU所执行的技术实现。同时详细分析了Java函式堆叠的创建全过程,在源码分析的过程中,带领读者从本质上理解到底什么是Java函式堆叠和栈帧,以及栈帧灶此内部的详细结构。 记忆体分配部分主要包括类型创建与载入、对象实例创建与记忆体分配,例如new关键字的工作机制,import关键字的作用,再如java.lang.ClassLoader.loadClass()接口的本地实现机制。 《揭秘Java虚拟机:JVM设计原理与实现》并不是简单地分析源码实现,而是在描述HotSpot内部实现机制的同时,分析了HotSpot如此这般实现的技术必然性。读者在阅读《揭秘Java虚拟机:JVM设计原理与实现》的过程中,将会在很多地方看到作者本人的这种思考。 目录 第1章 Java虚拟机概述 1 1.1 从机器语言到Java——詹爷,你好 1 1.2 兼容的选择:一场生产力的革命 6 1.3 中间语言翻译 10 1.3.1 从中间语言翻译到机器码 11 1.3.2 通过C程式翻译 11 1.3.3 直接翻译为机器码 13 1.3.4 本地编译 16 1.4 神奇的指令 18 1.4.1 常见汇编指令 20 1.4.2 JVM指令 21 1.5 本章总结 24 第2章 Java执行引擎工作原理:方法调用 25 2.1 方法调用 26 2.1.1 真实的机器调用 26 2.1.2 C语言函式调用 41 2.2 JVM的函式调用机制 47 2.3 函式指针 53 2.4 CallStub函式指针定义 60 2.5 _call_stub_entry例程 72 2.6 本章总结 115 第3章 Java数据结构与面向对象 117 3.1 从Java算法到数据结构 118 3.2 数据类型简史 122 3.3 Java数据结构之偶然性 129 3.4 Java类型识别 132 3.4.1 class位元组码概述 133 3.4.2 魔数与JVM内部的int类型 136 3.4.3 常量池与JVM内部对象模型 137 3.5 大端与小端 143 3.5.1 大端和小端的概念 146 3.5.2 大小端产生的本质原因 148 3.5.3 大小端验证 149 3.5.4 大端和小端产生的场景 151 3.5.5 如何解决位元组序反转 154 3.5.6 大小端问题的避免 156 3.5.7 JVM对位元组码档案的大小端处理 156 3.6 本章总结 159 第4章 Java位元组码实战 161 4.1 位元组码格式初探 161 4.1.1 准备测试用例 162 4.1.2 使用javap命令分析位元组码档案 162 4.1.3 查看位元组码二进制 165 4.2 魔数与版本 166 4.2.1 魔数 168 4.2.2 版本号 168 4.3 常量池 169 4.3.1 常量池的基本结构 169 4.3.2 JVM所定义的11种常量 170 4.3.3 常量池元素的复合结构 170 4.3.4 常量池的结束位置 172 4.3.5 常量池元素总数量 172 4.3.6 第一个常量池元素 173 4.3.7 第二个常量池元素 174 4.3.8 父类常量 174 4.3.9 变数型常量池元素 175 4.4 访问标识与继承信息 177 4.4.1 aess_flags 177 4.4.2 this_class 178 4.4.3 super_class 179 4.4.4 interface 179 4.5 栏位信息 180 4.5.1 fields_count 180 4.5.2 field_info fields[fields_count] 181 4.6 方法信息 185 4.6.1 methods_count 185 4.6.2 method_info methods[methods_count] 185 4.7 本章回顾 205 第5章 常量池解析 206 5.1 常量池记忆体分配 208 5.1.1 常量池记忆体分配总体链路 209 5.1.2 记忆体分配 215 5.1.3 初始化记忆体 223 5.2 oop-klass模型 224 5.2.1 两模型三维度 225 5.2.2 体系总览 227 5.2.3 oop体系 229 5.2.4 klass体系 231 5.2.5 handle体系 234 5.2.6 oop、klass、handle的相互转换 239 5.3 常量池klass模型(1) 244 5.3.1 klassKlass实例构建总链路 246 5.3.2 为klassOop申请记忆体 249 5.3.3 klassOop记忆体清零 253 5.3.4 初始化mark 253 5.3.5 初始化klassOop._metadata 258 5.3.6 初始化klass 259 5.3.7 自指 260 5.4 常量池klass模型(2) 261 5.4.1 constantPoolKlass模型构建 261 5.4.2 constantPoolOop与klass 264 5.4.3 klassKlass终结符 267 5.5 常量池解析 267 5.5.1 constantPoolOop域初始化 268 5.5.2 初始化tag 269 5.5.3 解析常量池元素 271 5.6 本章总结 279 第6章 类变数解析 280 6.1 类变数解析 281 6.2 偏移量 285 6.2.1 静态变数偏移量 285 6.2.2 非静态变数偏移量 287 6.2.3 Java栏位记忆体分配总结 312 6.3 从源码看栏位继承 319 6.3.1 栏位重排与补白 319 6.3.2 private栏位可被继承吗 325 6.3.3 使用HSDB验证栏位分配与继承 329 6.3.4 引用类型变数记忆体分配 338 6.4 本章总结 342 第7章 Java栈帧 344 7.1 entry_point例程生成 345 7.2 局部变数表创建 352 7.2.1 constMethod的记忆体布局 352 7.2.2 局部变数表空间计算 356 7.2.3 初始化局部变数区 359 7.3 堆叠与栈帧 368 7.3.1 栈帧是什么 368 7.3.2 硬体对堆叠的支持 387 7.3.3 栈帧开辟与回收 390 7.3.4 堆叠大小与多执行绪 391 7.4 JVM的栈帧 396 7.4.1 JVM栈帧与大小确定 396 7.4.2 栈帧创建 399 7.4.3 局部变数表 421 7.5 栈帧深度与slot复用 433 7.6 最大运算元栈与运算元栈复用 436 7.7 本章总结 439 第8章 类方法解析 440 8.1 方法签名解析与校验 445 8.2 方法属性解析 447 8.2.1 code属性解析 447 8.2.2 LVTLVTT 449 8.3 创建methodOop 455 8.4 Java方法属性复制 459 8.5 与 461 8.6 查看运行时位元组码指令 482 8.7 vtable 489 8.7.1 多态 489 8.7.2 C++中的多态与vtable 491 8.7.3 Java中的多态实现机制 493 8.7.4 vtable与invokevirtual指令 500 8.7.5 HSDB查看运行时vtable 502 8.7.6 miranda方法 505 8.7.7 vtable特点总结 508 8.7.8 vtable机制逻辑验证 509 8.8 本章总结 511 第9章 执行引擎 513 9.1 执行引擎概述 514 9.2 取指 516 9.2.1 指令长度 519 9.2.2 JVM的两级取指机制 527 9.2.3 取指指令放在哪 532 9.2.4 程式计数器在哪里 534 9.3 解码 535 9.3.1 模板表 535 9.3.2 汇编器 540 9.3.3 汇编 549 9.4 栈顶快取 558 9.5 栈式指令集 565 9.6 运算元栈在哪里 576 9.7 栈帧重叠 581 9.8 entry_point例程机器指令 586 9.9 执行引擎实战 588 9.9.1 一个简单的例子 588 9.9.2 位元组码运行过程分析 590 9.10 位元组码指令实现 597 9.10.1 iconst_3 598 9.10.2 istore_0 599 9.10.3 iadd 600 9.11 本章总结 601 第10章 类的生命周期 602 10.1 类的生命周期概述 602 10.2 类载入 605 10.2.1 类载入——镜像类与静态栏位 611 10.2.2 Java主类载入机制 617 10.2.3 类载入器的载入机制 622 10.2.4 反射载入机制 623 10.2.5 import与new指令 624 10.3 类的初始化 625 10.4 类载入器 628 10.4.1 类载入器的定义 628 10.4.2 系统类载入器与扩展类载入器创建 634 10.4.3 双亲委派机制与破坏 636 10.4.4 预载入 638 10.4.5 引导类载入 640 10.4.6 载入、连结与延迟载入 641 10.4.7 父载入器 645 10.4.8 载入器与类型转换 648 10.5 类实例分配 649 10.5.1 栈上分配与逃逸分析 652 10.5.2 TLAB 655 10.5.3 指针碰撞与eden区分配 657 10.5.4 清零 658 10.5.5 偏向锁 658 10.5.6 压栈与取指 659 10.6 本章总结 661
1、java对象内存布局分成对象头、实例数据部分和填充对其;
2、所有java对象的头部(Object Header)分成了两个基本部分:mark word和类型指针;
2、第一个部分是mark word 部分,放置了对象自身运行时的一些数据,梁斗包括HashCode、或渣耐 GC分代年龄、锁状态标志、偏向锁的线程ID等;它占用一个字宽的大小,在32和64位操作系统上,分别是32bit和64bit;mark word被设计为用最小的存储空间尽量的存储多类型的数据;它会在锁状态衫春不同的情况下,动态调整存储格式,以存储不同状态的数据;
3、 另一个部分存储了指向方法区对象类型数据的指针,如果是数组对象,还会有额外的一部分存储数组的长度。
也就是互斥锁,是传统的调用底层的mutex互斥量来实现的同步方式;所谓重量级,就是指其调用的资源占用还是相比较大,因为java目前主流的线程的实现还是一比一映射到内核线程之上的、线程的阻塞、唤醒都需要操作系统的协助来完成,这就避免不了用户态与和系统核心态的切换,这种状态的转换需要耗费很多的处理器时间。