Synchronize 底层原理

我正在参与「启航计划」

目标内存结构

Synchronize 底层原理总结

目标头MarkWord 存储目标头的信息,Klass Word 描绘目标实例的详细类型

实例数据:成员变量

对齐填充:假如目标头 + 实例变量 不是 8 的整数倍,则经过对齐填充补齐

MarkWord 解析

Synchronize 底层原理总结

  • hashcode:25位的目标标识Hash码
  • age:目标分代年龄占4位
  • biased_lock:倾向锁标识,占1位,0表明没有开端倾向锁,1表明敞开了倾向锁
  • thread:持有倾向锁的线程ID,占23位
  • epoch:倾向时刻戳,占2位
  • ptr_to_lock_record:轻量级锁状况下,指向栈中锁记录的指针,占30位
  • ptr_to_heavyweight_monitor:重量级锁状况下,指向目标监视器Monitor的指针,占30位

LockRecord 锁记录

Markword:记录锁记录的地址

目标引证:引证被加上锁了的目标

Synchronize 底层原理总结

重量级锁

Monitor

Monitor 监视器,是由 jvm 供给的,由 C++ 完成的,有三个完成部分

WaitSet:调用了 wait 方法的线程在这里等候,处于 WAITED 状况

EntryList:没有抢到目标锁的线程在这里等候,处于 BLOCKED 状况

Owner:存储已经抢到锁的线程目标

Monitor 的完成属于重量级锁,触及到 内核态和用户态的切换线程的上下文切换,每个 Java 目标都会关联一个 Monitor 目标,假如运用 Synchronize 给该目标加锁,那么 Java 目标上面的 MarkWord 地址就被设置为指向该 Monitor 目标的指针

Synchronize 底层原理总结

轻量级锁

加锁流程

  1. 在线程栈中创建一个 Lock Record 目标,它的 object reference 字段指向锁目标
  2. 经过 CAS 指令把 Lock Record 的地址存放到目标头的 Markword 中,假如是无锁状况则修正成功,代表该线程获取了轻量级锁
  3. 假如当时线程已经持有该锁,就代表是一次锁重入,设置 Lock Record 的第一部分为 null,起到一个重入计数器的效果
  4. 假如 CAS 修正失败,则阐明发生了竞赛,需求膨胀为重量级锁

解锁进程

  1. 遍历线程栈,找到一切 object reference 字段等于当时锁目标的 Lock record
  2. 假如 Lock recordMarkWordnull,代表这是一次重入,将 obj 设置为 nullcontinue 即可
  3. 假如 Lock recordMarkword 不为 null,则运用 CAS 指令将目标头的 markword 与目标目标头的 markword 进行替换,假如成功则康复无锁状况,假如失败膨胀重量级锁

Markword 记录

Synchronize 底层原理总结

开端时的状况

Synchronize 底层原理总结

替换后的状况

Synchronize 底层原理总结

倾向锁

背景:轻量级锁在没有竞赛的时分,每次重入都需求进行 CAS 操作

Java 6 中 引入倾向锁来做进一步的优化:只需第一次 操才运用 CAS 将线程 ID 设置到目标的 markword 头,之后发现这个线程 ID 是自己就不会产生竞赛,不用重新 CAS,今后只需不发生竞赛,这个目标就归这个线程一切

Synchronize 底层原理总结

代码示例:

public class Thread5 {
    private static final Object object = new Object();
    public static void method1() {
        synchronized (object) {
            method2();
        }
    }
    public static void method2() {
        synchronized (object) {
            method3();
        }
    }
    public static void method3() {
        synchronized (object) {
        }
    }
}

总结

Java 中的 Synchronize 有倾向锁、轻量级锁、重量级锁三种方式,分别对应了锁只被一个线程持有、不同线程替换持有锁、多线程竞赛的情况

重量级锁:底层运用 Monitor 完成,里面触及到了用户态和内核态的转换、进程的上下文切换,本钱较高,性能比较低

轻量级锁:线程加锁时刻是错开的(也就是没有竞赛),可以用轻量级锁来优化,轻量级修正了目标头的锁标志,相对重量级锁性能提升了许多,每次修正都是 CAS 操作,保证原子性

倾向锁:一段很长的时刻内都只被一个线程运用锁,可以运用倾向锁,第一次取得锁时,会有一个 CAS 操作,之后该线程再获取锁,只需求判断 mark word 中是否是自己的线程 id 即可,而不是开销相对较大的 CAS 指令