为什么在parkAndCheckInterrupt加断点后AQS自定义自旋打印多了一次?
问题原因分析
这个差异是调试器的隐式unpark操作导致的,核心逻辑和执行流差异如下:
先理清楚AQS acquireQueued 的基础执行逻辑
你添加的打印在for(;;)循环的第一行,每进入一次循环就会打印一次。正常情况下,acquireQueued中线程抢锁失败会进入parkAndCheckInterrupt阻塞,只有两种情况会从park中唤醒:
- 持有锁的线程调用
unlock触发unpark - 线程被其他线程调用
interrupt()中断
唤醒后会重新进入for(;;)循环尝试抢锁,抢锁失败就会再次阻塞。
正常运行时Thread-2的执行流
- 刚加入队列时前两次循环:两次尝试抢锁失败,调整前驱节点状态为SIGNAL后进入park阻塞,对应前两次打印
- 主线程调用
thread2.interrupt(),线程被唤醒,执行Thread.interrupted()返回true并清除中断标记,重新进入循环打印第三次,此时thread0还持有锁,抢锁失败再次进入park阻塞,没有后续唤醒操作,所以不会再打印
和你给出的正常结果完全匹配。
加断点后的额外执行逻辑
你在parkAndCheckInterrupt的return Thread.interrupted();行加了断点,这行的执行时机是park唤醒之后、清除中断标记之前:
- thread2被中断唤醒后命中断点,被调试器挂起
- 调试器为了恢复被断点挂起的线程,会隐式调用一次
LockSupport.unpark(thread2),给线程新增了一个可用的park许可 - 放行断点后,线程走完中断唤醒的原有逻辑,第三次打印后再次调用
park(),这时候直接消费了调试器额外给的unpark许可,不需要任何其他唤醒操作就直接返回 - 线程又一次进入
for(;;)循环,多打印了一次,之后再次抢锁失败进入正常阻塞
这就是多出来一次打印的原因。
验证方法
你可以把断点移到LockSupport.park(this)行之前,或者把断点的挂起策略改成「不挂起线程只打印日志」,就不会出现多打印的情况。
内容的提问来源于stack exchange,提问作者A Xuan of programmer
相关产品推荐
相关产品推荐

