【并发编程】ReentrantLock
程序员文章站
2022-05-04 20:53:18
...
ReentrantLock是什么
ReentrantLock继承自AbstractQueuedSynchronizer,即AQS, AQS可以理解为一个线程排队获取CPU的队列。队列中放的是一个个的Node。
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
volatile Node prev;
volatile Node next;
volatile Thread thread;
获取锁的过程
通过以下代码模拟多线程竞争锁的场景
public class Example1 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
ReentrantLock reentrantLock = new ReentrantLock(true);
for (int i = 0; i < 3; i++) {
threadPoolExecutor.submit(() -> {
reentrantLock.lock();
logic();
reentrantLock.unlock();
});
}
threadPoolExecutor.shutdown();
}
public static void logic() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
head == tail == null
- 第一个线程调用tryAcquire(arg)过来获取锁,此时state为0,说明锁是空闲状态,可以继续执行获取流程。
- 接下来尝试获取锁,首先通过hasQueuedPredecessors()判断队列有没有线程在排队,第一个线程过来时队列时空的,head==tail ==null,即没有线程排队。
- 直接尝试CAS获取锁
- 如果获取成功,记录锁被当前线程持有,setExclusiveOwnerThread(current)。
head == tail != null
- 第二个线程调用tryAcquire(arg)过来获取锁,此时state为1,不会继续执行获取锁的流程,但是会判断是否重入锁。
- 如果getExclusiveOwnerThread()是当前线程,则该线程可以得到锁。
- 否则进入队列
入队列之前会将当前线程封装成一个Node,Node的前一个节点prev为第一个线程对应的Node,Node的下一个节点next为null,即该Node为tail节点。
- 入队列之后继续尝试获取锁
// 当该节点的上一个节点是head节点时才尝试再次获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
- 第二个线程走到这里是满足条件的,可以获取到锁。
head != tail
- 第三个线程调用tryAcquire(arg)过来获取锁,此时state为1,不会继续执行获取锁的流程。
- 进入队列继续尝试获取锁。
- 第三个线程的前一个Node不是head,所以不能紧接着尝试获取锁。
- 判断是否需要park,第三个线程此时不需要park。(暂未模拟到需要park的场景)
- 尝试获取锁。
- 获取锁成功。
上一篇: 并发编程--ReentrantLock