目录
1. synchronized 底层是如何实现的
2. 监视器的执行流程
1. synchronized 底层是如何实现的
synchronized 底层是通过 JVM 内置的 Monitor 监视器实现的。
以下代码,查看它运行时的字节码文件:
public class SynchronizedMonitorDemo {
public static void main(String[] args) {
int count = 0;
synchronized (SynchronizedMonitorDemo.class) {
for(int i = 0; i < 10; ++i) {
count++;
}
}
System.out.println(count);
}
}
使用 IDEA 字节码插件 Bytecode Viewer 查看字节码文件:
从字节中可以看到两个指令:
- monitorenter:表示进入监视器
- monitorexit:表示退出监视器
由此可知 synchronized 底层是依赖 Monitor 监视器来实现的。
2. 监视器的执行流程
既然我们知道了 synchronized 的底层是依赖 Monitor 监视器来实现的,那么监视器它到底是怎么执行的呢 ?知其然还要知其所以然~
什么是监视器?
监视器它是一种机制,用来保障在任何时候,只有一个线程能够执行某个区域内的代码。
一个监视器可以看作是一栋房子,房子里有一个特殊的房间,这个房间同一时刻只能被一个线程所占有。占有该房间的线程可以共享该房间的所有数据。
进入该房子:进入监视器
进入该房间:获得监视器
离开该房间:释放监视器
离开该房子:退出监视器
在 hosSpot 虚拟机中,监视器底层是由 C++ 实现的,它的实现是一个结构体对象,它里面有以下几个重要属性:
- _count:记录该线程获取锁的次数;
- _recursions:记录锁的重入次数;
- _owner:表示拥有监视器对象的线程;
- _EntryList:表示监控集合,用于存放多线程情况下,竞争监视器失败的线程(处于阻塞状态的线程队列);
- _WaitSet:表示待授权集合,用于存放进入 waiting 状态的线程队列(当线程调用了 wait() 方法就会进入该集合)。
具体执行流程参考下图>>
它的执行流程大致是这样的 >>
- 线程通过 CAS(compare and swap) 尝试获取锁,如果获取成功了,就将 _owner 字段设置给当前线程,表示当前线程持有监视器锁,并将 _recusions 重入次数 + 1;如果获取失败,则通过 CAS 自旋不断尝试获取锁,尝试一定次数还是失败,那么就加入到 EntrySet 监控集合中(阻塞)。
- 当持有监视器锁的线程调用了 wait() 方法,当前线程就会释放锁,并将 _owner 字段恢复为 null,同时将当前线程加入到 WaitSet 待授权集合中,等待被唤醒。
- 当调用 notify() 方法时,随机唤醒 WaitSet 待授权集合中的某一个线程,当调用 notifyAll() 方法时,唤醒 WaitSet 待授权集合中所有的线程尝试获取锁。
- 线程执行完释放锁后,唤醒 EntrySet 监控集合中的所有的线程尝试获取锁。