likes
comments
collection
share

Synchronize 底层原理总结

作者站长头像
站长
· 阅读数 13

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 命令