synchronized 原理总结

介绍

在 Java 中 synchronized 是用于并发环境下使用临界区的原语。

它是通过获取对象头的 Mark Word 来实现互斥功能的,当使用方式不同时所操作的对象也不同。

  • 声明在方法内时,锁对象由开发者所指定

  • 声明在方法上,锁对象则为当前类的实例

  • 声明在静态方法上,锁对象就是由类加载器所加载的 Class

这里简单记录 synchronized 代码块在虚拟机内部的运转,版本为 OpenJDK11。

获取同步锁

当线程进入临界区时,会根据是否开启了偏向锁选择进入 ObjectSynchronizer::fast_enter 方法或者 ObjectSynchronizer::slow_enter 方法。

ObjectSynchronizer::fast_enter

  1. 检查当前 Mark Word 的锁状态是否为偏向锁,否则进入 slow_enter

  2. 检查虚拟机当前是否处于安全点,是则先进行检查是否需要撤销偏向锁。

  3. 检查对象头是否为偏向锁模式,否则使用 CAS 修改 Mark Word 存储的线程 id,并初始化获取锁时间点和年龄。

  4. 当已经处于偏向锁模式时,并且当前占有锁的时间已过期,则可以使用 CAS 进行锁替换或者更新年龄。

  5. 当任意 CAS 操作失败时,则表明存在其他线程已对锁状态进行了修改,需要执行 slow_enter

  6. 更新对象头信息,检查是否需要撤销偏向锁。

ObjectSynchronizer::slow_enter

  1. 判断 Mark Word 的锁状态是否为无锁或偏向锁,是则使用 CAS 修改对象头,成功后对当前 Region 栈锁进行释放。

  2. 否则判断 Mark Word 的锁状态是否为轻量级锁,并且对对象头的 BasicLock 进行加锁。

  3. 失败时将修改对象头使其不可重入,并将锁膨胀为重量级锁,修改 Mark Word。

  4. 通过 ObjectMonitor 管理当前线程,当对象头为轻量级锁时,获取空闲 ObjectMonitor 使用,并配置至对象头内。

  • ObjectMonitor 的获取方式为依次从 线程空间中查找空闲监视器全局空间中查找空闲监视器重新创建监视器 选取尝试。

等待与唤醒

wait

当处于临界区内的线程由于某种原因选择将自身挂起时,将执行 ObjectSynchronizer::wait

  1. 判断当前 Mark Word 是否为偏向锁,是则对其进行撤销。

  2. 释放临界区占用锁,获取管理当前线程的的 ObjectMonitor

  3. 将线程封装为 ObjectWaiter 加入至 _WaitSet 队尾。

  4. 设置 LoadLoad 内存屏障,防止重排序问题。

  5. 判断是否该等待线程是否已经存在,是否重新启动该线程。

  6. 关闭非运行线程与重入锁的响应。

  7. 判断线程是否已被中断,是则抛出异常。

notify/notifyAll

  1. 检查等待队列 _WaitSet,为空则直接返回。

  2. 使用 _WaitSetLock 保护操作等待队列不被其他线程干扰。

  3. 根据策略选择从等待队列中获取元素加入到阻塞队列 _EntryList_cxq 或 直接唤醒。

  4. 设置被移动线程为可与重入锁响应。

  5. 释放 _WaitSetLock 锁。

阻塞线程

enter

当进入临界区的线程获取锁失败时,将交由锁对象的 objectMonitor 来管理,调用 enter 方法修改该线程状态为阻塞,并封装为 ObjectWaiter 加入到名为 _EntryList 的链表中。

exit

当有线程离开临界区,将会调用 objectMonitor 的 exit 方法,根据 QMode 的不同的选择从 阻塞队列 或 等待队列 中获取 ObjectWaiter,也可能将等待队列的部分或全部加入阻塞队列后从阻塞队列中获取。