背景介绍
我们知道,基于AbstractQueuedSynchronizer线程同步器可以很方便的实现锁的一些高级特性,比如:
- 公平性、非公平性
- 可重入特性
- 超时机制
- 响应中断
- 共享、独享
举个例子,ReentrantLock就是一个具有很多高级特性的锁。从名字中就可以看出来这是一个带有重入功能的锁;ReentrantLock是独享锁;创建ReentrantLock可以通过参数指明是公平模式还是非公平模式,就可以实现公平锁或非公平锁;ReentrantLock#lockInterruptibly()和带时间参数的ReentrantLock#tryLock()可以响应中断。
从代码实现上来看,功能如此丰富的ReentrantLock代码实现居然不足两百行(去掉注释和空行),而大部分逻辑实现都是委托给AbstractQueuedSynchronizer。
那么问题来了,AbstractQueuedSynchronizer到底是什么呢?我们来探究一下。
AbstractQueuedSynchronizer设计与原理
首先来看一下继承结构:
- AbstractOwnableSynchronizer仅实现的功能是维护了独占的线程。
- 核心功能全部都是在AbstractQueuedSynchronizer实现。
然后,我们以具体的代码来深入分析AbstractQueuedSynchronizer的原理:
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
try{
lock.lock();//加锁操作
// 业务处理
}finally{
lock.unlock();
}
}
这里默认使用的是非公平锁,假设程序依次执行如下过程:线程一加锁、线程二加锁、线程一解锁、线程二解锁,ReentrantLock内部遍变化如下图所示:
过程一:线程一t1加锁即调用lock(),如图1所示:
使用cas将state设置为1,代表加锁成功,同时将exclusiveOwnerThread设置为t1。线程一t1开始执行业务代码。
过程二:线程二t2来加锁即调用lock(),图2所示:
线程一:继续执行业务代码。 线程二:加锁失败,进入队列(首次会创建自身节点与Head节点),线程二处于休眠状态。
过程三:线程一t1解锁即调用unlock(),图3图4所示:
线程一:使用cas将state设置为0,代表解锁成功,同时将独占exclusiveOwnerThread设置为null。 线程二:被唤醒,继续执行代码。
线程二:被唤醒后尝试加锁,加锁成功,开始执行业务代码。 加锁逻辑会将头节点设置为当前节点(前一个头结点会被垃圾回收),并且将当前节点的thread设置为null;
过程四:线程二t2解锁即调用unlock(),如图5所示:
线程二:使用cas将state设置为0,代表解锁成功,同时将exclusiveOwnerThread设置为null。
总结一下
从以上的几个过程来看,ReentrantLock的lock()与unlock()核心都是操作其state状态和wait队列。
- state:状态,比如是否持有锁或获取锁的次数。
- wait队列:代表等待的线程,Head节点表示持有锁正在运行的线程。
如下图所示:
从代码的实现来看,AbstractQueuedSynchronizer实现了绝大多数的逻辑。从加锁流程来看:这里使用了模板方法设计模式,尝试加锁的动作交给子类实现,后续的逻辑如创建队列Waiter节点、入队列、阻塞都是在AbstractQueuedSynchronizer中实现的;从解锁流程来看:这里同样使用了模板方法设计模式,尝试解锁的动作交给子类实现,后续逻辑如唤醒后继节点及后继节点设置头节点等都是在AbstractQueuedSynchronizer中实现。
特性的实现原理
公平性
主要体现在子类尝试加锁的过程,即tryAcquire()方法实现:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这是非公平的尝试加锁逻辑:不管队列有没有排队,直接使用cas尝试修改state即加锁。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这是公平的尝试加锁逻辑:认为队列的优先级更高,只有在队列没有线程排队的情况之下才会去尝试加锁,否则直接返回尝试失败,进行后续逻辑。
可重入性
每一次执行lock()方法,state的值就会递增1,解锁流程同样会递减1,直至减为0代表解锁。
超时机制
主要逻辑:尝试加锁失败,如果在截止时间之前被唤醒并获得锁,则加锁成功;否则,休眠固定时间后自动苏醒,并根据截止时间返回即加锁失败。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
响应中断
注意:并不是所有的方法都能响应中断、并不是立刻能响应的。
共享锁、独占锁
独占锁只能有一个线程持有锁,如ReentrantLock。 共享锁可以有多个线程持有锁,CountDownLatch。 如CountDownLatch,初始化state值代表有几个线程共享,每个线程执行完都会尝试释放锁,即state递减1,当state减为0的时候,会唤醒队列中阻塞的线程。调用awit()方法的线程,如果此时state不为0就会阻塞在队列中。