synchronized 原理总结
介绍
在 Java 中 synchronized
是用于并发环境下使用临界区的原语。
它是通过获取对象头的 Mark Word 来实现互斥功能的,当使用方式不同时所操作的对象也不同。
-
声明在方法内时,锁对象由开发者所指定
-
声明在方法上,锁对象则为当前类的实例
-
声明在静态方法上,锁对象就是由类加载器所加载的 Class
这里简单记录 synchronized
代码块在虚拟机内部的运转,版本为 OpenJDK11。
获取同步锁
当线程进入临界区时,会根据是否开启了偏向锁选择进入 ObjectSynchronizer::fast_enter
方法或者 ObjectSynchronizer::slow_enter
方法。
ObjectSynchronizer::fast_enter
-
检查当前 Mark Word 的锁状态是否为偏向锁,否则进入
slow_enter
。 -
检查虚拟机当前是否处于安全点,是则先进行检查是否需要撤销偏向锁。
-
检查对象头是否为偏向锁模式,否则使用 CAS 修改 Mark Word 存储的线程 id,并初始化获取锁时间点和年龄。
-
当已经处于偏向锁模式时,并且当前占有锁的时间已过期,则可以使用 CAS 进行锁替换或者更新年龄。
-
当任意 CAS 操作失败时,则表明存在其他线程已对锁状态进行了修改,需要执行
slow_enter
。 -
更新对象头信息,检查是否需要撤销偏向锁。
ObjectSynchronizer::slow_enter
-
判断 Mark Word 的锁状态是否为无锁或偏向锁,是则使用 CAS 修改对象头,成功后对当前 Region 栈锁进行释放。
-
否则判断 Mark Word 的锁状态是否为轻量级锁,并且对对象头的 BasicLock 进行加锁。
-
失败时将修改对象头使其不可重入,并将锁膨胀为重量级锁,修改 Mark Word。
-
通过
ObjectMonitor
管理当前线程,当对象头为轻量级锁时,获取空闲 ObjectMonitor 使用,并配置至对象头内。
- ObjectMonitor 的获取方式为依次从
线程空间中查找空闲监视器
、全局空间中查找空闲监视器
、重新创建监视器
选取尝试。
等待与唤醒
wait
当处于临界区内的线程由于某种原因选择将自身挂起时,将执行 ObjectSynchronizer::wait
。
-
判断当前 Mark Word 是否为偏向锁,是则对其进行撤销。
-
释放临界区占用锁,获取管理当前线程的的
ObjectMonitor
。 -
将线程封装为
ObjectWaiter
加入至_WaitSet
队尾。 -
设置 LoadLoad 内存屏障,防止重排序问题。
-
判断是否该等待线程是否已经存在,是否重新启动该线程。
-
关闭非运行线程与重入锁的响应。
-
判断线程是否已被中断,是则抛出异常。
notify/notifyAll
-
检查等待队列
_WaitSet
,为空则直接返回。 -
使用
_WaitSetLock
保护操作等待队列不被其他线程干扰。 -
根据策略选择从等待队列中获取元素加入到阻塞队列
_EntryList
或_cxq
或 直接唤醒。 -
设置被移动线程为可与重入锁响应。
-
释放
_WaitSetLock
锁。
阻塞线程
enter
当进入临界区的线程获取锁失败时,将交由锁对象的 objectMonitor
来管理,调用 enter
方法修改该线程状态为阻塞,并封装为 ObjectWaiter
加入到名为 _EntryList
的链表中。
exit
当有线程离开临界区,将会调用 objectMonitor 的 exit
方法,根据 QMode 的不同的选择从 阻塞队列 或 等待队列 中获取 ObjectWaiter
,也可能将等待队列的部分或全部加入阻塞队列后从阻塞队列中获取。