1. monitorenter获取锁
每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:
- 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为
monitor的owner(所有者) - 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
- 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直
到monitor的进入数变为0,才能重新尝试获取monitor的所有权。
2. monitorexit释放锁
monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。
- 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
- 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出
monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个
monitor的所有权。
面试题synchroznied出现异常会释放锁吗?
会释放锁。
3. monitor监视器锁
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; // 线程的重入次数
_object = NULL; // 存储该monitor的对象
_owner = NULL; // 标识拥有该monitor的线程
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL;
_succ = NULL;
_cxq = NULL; // 多线程竞争锁时的单向列表
FreeNext = NULL;
_EntryList = NULL; // 处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0;
_SpinClock = 0;
OwnerIsThread = 0;
}
- _owner:初始时为NULL。当有线程占有该monitor时,owner标记为该线程的唯一标识。当线程
释放monitor时,owner又恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线
程安全的。 - _cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq是一个临界资
源,JVM通过CAS原子指令来修改_cxq队列。修改前_cxq的旧值填入了node的next字段,_cxq指
向新值(新线程)。因此_cxq是一个后进先出的stack(栈)。 - _EntryList:_cxq队列中有资格成为候选资源的线程会被移动到该队列中。
- _WaitSet:因为调用wait方法而被阻塞的线程会被放在该队列中。
ObjectMonitor的数据结构中包含:_owner、_WaitSet和_EntryList,它们之间的关系转换可以用下图
表示:
1.monitor竞争
- 通过CAS尝试把monitor的owner字段设置为当前线程。
- 如果设置之前的owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行
recursions ++ ,记录重入的次数。 - 如果当前线程是第一次进入该monitor,设置recursions为1,_owner为当前线程,该线程成功获
得锁并返回。 - 如果获取锁失败,则等待锁的释放。
2.monitor等待
- 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ。
- 在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node
节点push到_cxq列表中。 - node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当
前线程挂起,等待被唤醒。 - 当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。
3.monitor释放
- 退出同步代码块时会让_recursions减1,当_recursions的值减为0时,说明线程释放了锁。
- 根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过
ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由unpark完成
来源:https://www.bilibili.com/video/BV1aJ411V763?p=19&spm_id_from=pageDriver