上节,我们讲了AQS的阻塞与释放实现原理,线程间通信(Condition)的原理。这次,我们就讲讲基于AQS实现的ReentrantLock(重入锁)。
1. 介绍
结合上面的ReentrantLock类图,ReentrantLock实现了Lock接口,它的内部类Sync继承自AQS,绝大部分使用AQS的子类需要自定义的方法存在Sync中。而ReentrantLock有公平与非公平的区别,即’是否先阻塞就先获取资源’,它的主要实现就是FairSync与NonfairSync,后面会从源码角度看看它们的区别。
2. 源码剖析
Sync是ReentrantLock控制同步的基础。它的子类分为了公平与非公平。使用AQS的state代表获取锁的数量
1 |
|
我们可以看出内部类Sync是一个抽象类,继承它的子类(FairSync与NonfairSync)需要实现抽象方法lock。
下面我们先从非公平锁的角度来看看获取资源与释放资源的原理 故事就从就两个变量开始:
1 |
|
2.1 上锁(获取资源)
1 |
|
1 |
|
1 |
|
AQS的acquire
1 |
|
上一节已经剖析过AQS的acquire的整个流程了,就差子类如何去实现tryAcquire了。
1 |
|
尝试获取资源的过程是非常简单的,这里再贴一下acquire的流程图
2.2 释放资源
1 |
|
1 |
|
AQS的release
1 |
|
release的流程已经剖析过了,接下来看看tryRelease的实现
1 |
|
tryRelease的实现也很简单,这里再贴一下release的流程图
2.3 公平锁与非公平锁的区别
公平锁与非公平锁,即’是否先阻塞就先获取资源’, ReentrantLock中公平与否的控制就在tryAcquire中。下面我们看看,公平锁的tryAcquire
1 |
|
区别在代码(2.3.1) hasQueuedPredecessors
判断当前线程的前面有无其他线程排队;若当前线程在队列头部或者队列为空返回false
1
2
3
4
5
6
7
8
9
10public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
结合下面的入队代码(enq), 我们分析hasQueuedPredecessors为true的情况:
- h != t ,表示此时queue不为空; (s = h.next) == null, 表示另一个结点已经运行了下面的步骤(2),还没来得及运行步骤(3)。简言之,就是B线程想要获取锁的同时,A线程获取锁失败刚好在入队(B入队的同时,之前占有的资源的线程,刚好释放资源)
-
h != t 且 (s = h.next) != null,表示此时至少有一个结点在sync queue中;s.thread != Thread.currentThread(),这个情况比较复杂,设想一下有这三个结点 A -> B C, A此时获取到资源,而B此时因为获取资源失败正在sync queue阻塞,C还没有获取资源(还没有执行tryAcquire)。
2.1 时刻一:A释放资源成功后(执行tryRelease成功),B此时还没有成功获取资源(C执行s = h.next时,B还在sync queue中且是老二)
2.2 时刻二: C此时执行hasQueuedPredecessors,s.thread != Thread.currentThread()成立,此时s.thread表示的是 B
1 |
|
Note that 1. because cancellations due to interrupts and timeouts may occur at any time, a true return does not guarantee that some other thread will acquire before the current thread(虚假true). 2. Likewise, it is possible for another thread to win a race to enqueue after this method has returned false, due to the queue being empty(虚假false).
这位大佬对hasQueuedPredecessors进行详细的分析,他文中解释了虚假true以及虚假false。我这里简单解释一下:
- 虚假true, 当两个线程都执行tryAcquire,都执行到hasQueuedPredecessors,都返回true,但是只有一个线程执行 compareAndSetState(0, acquires) 成功
- 虚假false,当一个线程A执行doAcquireInterruptibly,发生了中断,还没有清除掉被该结点时;此时,线程B执行hasQueuedPredecessors时,返回true
3. 总结
本文介绍了利用AQS实现的锁ReetrantLock
- 讲解了tryRelease与tryAcquire的实现原理
- 说了说锁的公平与否的实现,是否在意当前线程是否有其他线程排队
- 分析了一下hasQueuedPredecessors的几种情况