共计 1993 个字符,预计需要花费 5 分钟才能阅读完成。
前言
以前咱们在温习 synchronized 的时候,和 lock 一比照,总说它是一个重量级锁,性能很差,不如 lock 来的不便,但其实 synchronized 作为 Java 中元老级的关键字,很多 jdk 原始代码都有用到它,因而 Java 开发团队还是十分中意这个“私生子”的,所以为了给它减重,开发者也早在 Java6 就为 synchronized 专门设计了一套优化处理过程。
一、synchronized 应用
- 加到办法上,锁就是这个对象,避免多个线程同时拜访这个对象的 synchronized 办法;
- 加到静态方法,锁就是这个类的 class 对象,避免多个线程同时拜访这个类的 synchronized 办法,对该类所有对象实用;
- 加到代码块,锁就是该代码块所在的对象,避免多个线程同时拜访该代码块;
二、JVM 对 synchronized 的实现形式
- 代码块同步:通过应用 monitorenter 和 monitorexit 指令实现的
- 同步办法:ACC_SYNCHRONIZED 润饰
三、Java 对象头(存储锁类型)
理解锁降级之前,必须先晓得 Java 对象头,正式对象头记录的信息才实现了锁降级过程中的线程判断。
Java 对象:
Java 对象头:蕴含两局部:MarkWord 和 类型指针
MarkWord:Mark Word 用于存储对象本身的运行时数据,如 HashCode、GC 分代年龄、锁状态标记、线程持有的锁、偏差线程 ID 等等。
占用内存大小与虚拟机位长统一(32 位 JVM -> MarkWord 是 32 位,64 位 JVM -> MarkWord 是 64 位)
四、锁降级过程
无锁状态 -> 偏差锁 -> 轻量级锁(自旋锁,自适应自旋)-> 重量级锁
各种锁状态下 mark word 的内容:
状态 | 偏差锁位 | 标记位 | 存储内容 |
---|---|---|---|
未锁定 | 0 | 01 | 对象哈希码、对象分代年龄 |
偏差锁 | 1 | 01 | 偏差线程 ID、偏差工夫戳、对象分代年龄 |
轻量级锁定 | 0 | 00 | 指向锁记录的指针 |
收缩(重量级锁定) | 0 | 10 | 执行重量级锁定的指针 |
GC 标记 | 0 | 11 | 空(不须要记录信息) |
无锁状态 -> 偏差锁
首先当处于无锁状态时,MarkWord 中存储的是对象的 hash code 等信息,偏差锁为 0,此时线程 1 进入,发现没有记录线程 ID 信息,则间接 CAS 操作将本人的线程 ID 设置进去,设置胜利则获取到偏差锁,偏差锁标记为改为 1;失败则要降级为轻量级锁;
线程 1 执行完后,将 markWord 中的线程 ID 设置为空,即开释偏差锁;
如果在线程 1 开释之前,此时线程 2 进入,先查 markWord 中记录的线程 ID 是否是本人的(这也是偏差锁实用的场景:教训表明,大多数状况下,都是同一个线程进入同一个同步代码块),若是,则获取锁胜利;
不是,则阐明曾经偏差给了其余线程,即呈现了线程竞争的状况,此时个别状况下,偏差锁是 不能够重偏差 的,所以须要降级,如果是能够重偏差的,则会尝试 CAS 操作替换 markWord 中的线程 id,胜利则偏差给线程 2,失败则进行锁降级;
偏差锁在降级前,须要先将偏差锁撤销。
如果此时曾经偏差给了某个线程,则须要依据 markWord 中记录的线程 id 查问该线程是否存活,不存活,则进入无锁状态,或者可重偏差状态,存活则撤销偏差锁,将锁对象的 markWord 复制到线程栈中,开始降级
偏差锁 -> 轻量级锁
呈现两个及以上线程竞争时(曾经偏差给了一个线程,此时又呈现了另外一个线程竞争,即降级),开始降级为轻量级锁。
加锁时,线程会在本人的栈帧中创立一个 Lock Record锁记录(会将对象原来的 markWord 内容复制过去,作为一个 lockRecord,蕴含对象分代年龄等信息,而原来的 markWord 只会有一个指针),同时CAS 将锁对象的 markWord 中替换为指向该 lockRrecord 的指针,操作胜利则获取到轻量级锁;
线程执行完,解锁时,只须要移除栈帧中的 lockRecord 即可,而不必批改 markWord 中的内容。
轻量级锁 -> 重量级锁
设置失败,则证实有竞争,此时会进入 自旋 状态。
线程超过 10 次自旋,或者自旋线程数超过 CPU 核数的一半,(JDK1.6 之后,退出自适应自旋 Adapative Self Spinning,JVM 本人管制)。就会降级为重量级锁。
降级重量级锁:
向操作系统申请资源,CPU 从 3 级 - 0 级零碎调用,线程挂起,进入期待队列,期待操作系统的调度,而后再映射回用户空间
所以降级为重量级锁后,用户线程被零碎线程挂起,须要从 用户态 到内核态 的转换。这也是最开始说 synchronized 重,性能差的起因!
总结:
- synchronized锁降级过程是单向的,不可逆;
- 降级过程围绕锁对象中 markWord 的内容变动;
- 每种锁都适应不同的场景,比方偏差锁实用于同一个线程屡次进入同步代码的状况,轻量级锁实用竞争小,且同步执行工夫短(自旋一会儿就能获取到锁)的状况
- 对了,synchronized 是可重入的,这点不光能够从偏差锁看进去,即便降级到重量级锁,也是反对的