ReentrantLock$NonfairSync#lock方法详解
总结性概述
- 获取锁操作:CAS原子操作state(0->1)
- 获取失败后,先进先出尾部插入CLH队列中
- 通过CLH队列的前缀节点的waitStatus状态是否Node.SIGNAL决定是否阻塞:LockSupport.park
- LockSupport.park响应中断,但不抛出InterruptedException异常
- CLH队列的头节点是占有锁的节点,如果其waitStatus不等于0,则说明后面有排队的
源码调式demo介绍
@Test
public void testLockAndUnlock() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try{
lock.lock();
System.out.println("断点中,模拟占有锁");//1
} finally {
lock.unlock();//3
}
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try{
lock.lock();//2
System.out.println("阻塞释放");
} finally {
lock.unlock();
}
}
}, "thread2");
thread1.start();
thread2.start();
Thread.sleep(1000 * 60 * 30);
}
- 启用IDEA的线程端点模式
1.1 在thread1中1处断点,模拟thread1占有锁但未释放
1.2 切换到thread2时,到lock.lock()时会因未获取锁而阻塞
根据源码分析加锁原理
- 非公平锁体现if逻辑块里,在入队前先尝试获取锁
1.1 获取锁主要CAS操作,将state属性原子性从0修改为1,然后设置获取锁的线程(方便后面重入操作)
尝试获取锁ReentrantLock$Sync#nonfairTryAcquire
- 上图红色部分体现重入锁的概念,同一线程可以多次加锁
CLH 思想的实现 AbstractQueuedSynchronizer#addWaiter
维护链表
- 将当前线程信息封装成Node
- enq(node) 自旋操作
2.1 tail节点初始化为null ,进行CAS操作初始化,tail = head = new Node()
2.2 操纵pre、next指针维护CLH链表(尾部插入)
以独占不可中断模式获取已经在队列中的线程(用于condition wait以及acquire)
- 获取当前Node的前置节点
1.1 如果是头节点则尝试获取锁(失败,是头但未释放)
1.2 获取锁成功后,将次node设为head
1.3 将之前的head从链表中移除(p.next = null) - 判断是否应该阻塞:shouldParkAfterFailedAcquire(p, node)
2.1 从尾一直往前找,直到找到waitStatus<=0的node
2.2 waitStatus不等于-1(Node.SIGNAL),会初始化成-1
2.3 当waitStatus=Node.SIGNAL时,返回true - 阻塞并检查是否中断:parkAndCheckInterrupt()
3.1 通过 LockSupport.park(this)进行阻塞
(1)响应中断,但不会抛InterruptedException异常
(2)当释放锁时 LockSupport.unpark(thread1),会解除阻塞继续执行
3.2 Thread.interrupted() 检测是否中断,并清除中断位
========== 至此加锁过程分析完成 ======================
AbstractQueuedSynchronizer#release方法详解
- 尝试释放锁
1.1 释放过程:state - 1 是否等于0
(1)是则可以释放,将独占线程属性exclusiveOwnerThread置为null - 如果head节点(加锁线程节点)waitStatus不等于0,则说明后面有排队的node
2.1 避免释放,先将其waitStatus设置为0
2.2 如果头节点的下一节点的waitStatus>0(取消),则从尾往头找,找到最靠近头的waitStatus <= 0的节点(为什么尾部寻址呢?)
2.3 如果找到,则取消阻塞状态:LockSupport.unpark(s.thread);
疑问点
- 为什么中断线程后复位中断, LockSupport.park(lock)将执行两次才会阻塞?
1.1 demo演示
(1)场景还原- thread1通过LockSupport.park(lock)阻塞住
- thread2中将thread1中断:thread1.interrupt();
2.1 thread1的LockSupport.park(lock)响应中断,解除阻塞继续执行
2.2 thread1恢复中断位:Thread.interrupted();
(2)结果显示:及时恢复了中断位后,LockSupport.park(lock);执行了一次后再阻塞
- 解锁时,为什么要尾部寻址找到最靠近head的节点,然后再通知