前言
相较于 Synchronized 使用方法的学习来说,学习 Synchronized 的实现原理是更深的一个层次,这部分知识的掌握难度要较高一些,尽管如此,这篇文章在一定程度上,提供了学习 Synchronized 原理所需要掌握的知识,希望读者朋友可以耐心的看下去,相信你看到文章一半以后会感觉到越来越接近最初的目标。
1. 实现 Synchronized 的基础
- Java 对象头
- Monitor
1.1 对象头的 Mark Word 的结构

Q : 为什么设计为非固定结构?
A : 由于对象头中的信息是与对象自身数据没有关系的额外存储成本,考虑到 Java 虚拟机的空间效率,Mark Word 被设计成一个非固定的数据结构以便存储更多有效的数据。具体地,它会根据对象状态来复用这块存储空间。
1.2 Monitor 对象锁
每个对象都天生自带了一把看不见的锁,即 Monitor 对象锁,它自身也是一个对象
实现方式有两种:
- Monitor 对象与数据对象一起创建、一起销毁
- 当线程获取 Monitor 对象锁时自动生成
ObjectMonitor:对象锁的实现
// http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/29ef249e9953/src/share/vm/runtime/objectMonitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0; // 当前持有对象锁的线程数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL; // 当前持有对象锁的线程
_WaitSet = NULL; // 等待池
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 锁池
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
其中,锁池 _EntryList 和 等待池 _WaitSet 是用来保存 ObjectWaiter 对象的列表,ObjectWaiter 是线程的封装。
当多个线程同时访问一个对象的同步代码时,首先进入 _EntryList,当一个 _EntryList 中的线程获取到对象锁后就进入到同步区域,并把 ObjectMonitor 中的 _Owner 设置成当前线程,计数器 _Count++。若线程调用 wait 方法,将释放对象锁, _Owner 被恢复为 null, _Count--,该线程进入到 _WaitSet 中等待被唤醒;若当前线程执行完毕,也会释放对象锁,并复位对应变量的值。

2. Synchronized 的效率优化
早期版本中,Monitor 是基于操作系统的 Mutex Lock 实现的,操作系统实现的线程切换需要从用户态转换到核心态,开销较大。
Java6 引入了锁优化技术,使得 Synchronized 的性能得到了较大提升,锁优化技术如下:
- 自适应自旋锁
- 锁消除
- 锁粗化
- 轻量级锁
- 偏向锁
2.1 自旋锁与自适应自旋锁
共享数据锁定时间短,对与阻塞线程所做的挂起和恢复线程操作并不值得。在这种情况下,可使这些线程不放弃 CPU 而自旋(忙等待)来等待锁的释放
缺点:若锁定时间较长,自旋的线程会白白消耗 CPU 资源
由于每个锁的锁定时间不同,也就是说并不是每个锁都适合线程以自旋的方式去等待锁,那么如何判断一个锁是否适合自旋呢?通过前一次获取锁的线程的自旋的时间以及该线程的状态决定,若上一个线程通过自旋的方式成功地获取了锁,那么 JVM 认为自旋获取锁的可能性较大,会自动增加等待时间,比如增加 50 次循环;相反,如果自旋很少成功地获取到锁的话,JVM 可能会省略自旋过程,以避免浪费处理器资源。
2.2 锁消除
JIT 编译时,对运行时上下文进行扫描,去除那些不可能存在多线程竞争的锁
2.3 锁粗化
原则上,我们会尽量减少同步块的作用范围,即只在共享变量的实际作用域中才进行同步,这样是为了减少一些操作的不必要同步,也可以让等待锁的线程尽早的拿到锁。
如果存在一连串操作要对同一对象反复进行加锁和解锁,即使没有线程竞争,频繁地进行互斥同步锁操作会产生不必要的性能损耗。
为了解决上述问题,我们通过扩大锁的范围,避免多次反复的加锁和解锁来实现,这被称为锁粗化。