synchronized原理

在这里插入图片描述

1. monitorenter获取锁

每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:

  1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为
    monitor的owner(所有者)
  2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
  3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直
    到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

2. monitorexit释放锁

monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。

  1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
  2. 执行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;
}
  1. _owner:初始时为NULL。当有线程占有该monitor时,owner标记为该线程的唯一标识。当线程
    释放monitor时,owner又恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线
    程安全的。
  2. _cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq是一个临界资
    源,JVM通过CAS原子指令来修改_cxq队列。修改前_cxq的旧值填入了node的next字段,_cxq指
    向新值(新线程)。因此_cxq是一个后进先出的stack(栈)。
  3. _EntryList:_cxq队列中有资格成为候选资源的线程会被移动到该队列中。
  4. _WaitSet:因为调用wait方法而被阻塞的线程会被放在该队列中。

ObjectMonitor的数据结构中包含:_owner、_WaitSet和_EntryList,它们之间的关系转换可以用下图
表示:
在这里插入图片描述

1.monitor竞争
  1. 通过CAS尝试把monitor的owner字段设置为当前线程。
  2. 如果设置之前的owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行
    recursions ++ ,记录重入的次数。
  3. 如果当前线程是第一次进入该monitor,设置recursions为1,_owner为当前线程,该线程成功获
    得锁并返回。
  4. 如果获取锁失败,则等待锁的释放。
2.monitor等待
  1. 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ。
  2. 在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node
    节点push到_cxq列表中。
  3. node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当
    前线程挂起,等待被唤醒。
  4. 当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。
3.monitor释放
  1. 退出同步代码块时会让_recursions减1,当_recursions的值减为0时,说明线程释放了锁。
  2. 根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过
    ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由unpark完成

来源:https://www.bilibili.com/video/BV1aJ411V763?p=19&spm_id_from=pageDriver

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值