AQS之ReentrantLock
ReentrantLock中通过创建内部类Sync继承AbstractQueuedSynchronizer
来管理同步状态,Sync又有公平和非公平锁两种模式:
公平锁
在公平锁模式下调用ReentrantLock.lock(),内部调用FairSync.lock(),其中调用AQS的acquire(1)获取一个独占锁。AQS并未实现tryAcquire()方法,由FaciSync实现。在tryAcquire()中:
- 首先检查state状态,若有锁定并且锁定线程不是当前线程则返回false。
- 若无锁定并且同步队列中无等待线程,则使用CAS将state改为锁定状态,若成功则将当前线程设为独占锁拥有者。
- 若当前线程为独占锁拥有者(重入),则将state加一,不需要使用CAS,因为已经拥有独占锁。
final void lock() {
acquire(1);
}
// AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// FairSync
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;
}
}
若tryAcquire()无法获取到锁,则调用AQS.addWaiter(Node mode),先将当前线程包装成一个独占类型的同步队列结点,如果Tail结点不为空,则将当前Node的prev指针指向Tail,Tail的next指针指向当前node。然后调用AQS.enq()将当前Node加入同步队列中,若Tail结点为空,创建一个新的空Node,赋值给Head/Tail。再将当前Node前置prev指向Tail,Tail的后继next指向当前Node。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
将当前线程包装成结点放入同步队列之后,调用AQS.acquireQueued()方法,当前线程会一直在所属结点的前驱上轮询:
- 当前驱结点为Head时,证明当前结点在所有等待结点中排在第一个。调用FairSync.tryAcquire()尝试加锁,如果成功则将当前结点与Head结点的互相引用解绑,将当前结点设为Head,Thread属性清空,当前线程开始执行临界区代码。
- 前驱不为Head时,检查当前线程是否应该阻塞,若要阻塞,阻塞时需要检查线程是否被打断。检查线程是否应该被阻塞时,需要用到前驱结点状态waitStatus:
若waitStatus = Node.SIGNAL,表示应该阻塞当前线程。
若waitStatus = Node.CANCEL,表示前驱结点已经被取消,应该将前驱指针指向前驱的前驱。若前驱状态仍为CANCEL循环之前的替换。直至前驱waitStatus!=CANCEL,将前驱Next指向当前结点,不阻塞当前线程。
若waitStatus= INTITAL or PROPAGATE,使用CAS操作更新前驱结点waitStatus至SIGNAL,不阻塞当前线程。
如果线程在被等待过程中被打断,返回true。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
非公平锁
非公平锁的加锁过程相较于公平锁,只少了一个步骤。在尝试加锁时,公平实现会检查同步队列中是否有前驱结点,若有则不尝试加锁,将当前线程包装结点放入队列;而非公平实现不会检查同步队列中是否有元素,直接尝试加锁,若加锁不成功再放入同步队列之中。
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;
}
unlock
Reentrant.unlock()调用AQS.release():
- 首先使用Sync.tryRelease(),若state-release = 0,则将独占锁拥有线程设为Null,再将state更新为0,返回true,否则返回false。
- 若第一步锁已经释放,Head结点不为Null并且waitStatus != 0则唤醒后续结点线程。若=0,则可以判断队列后续无结点或者结点并没有被打断,无需唤醒。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// h.waitStatus != 0 等价于 h.waitStatus == -1(SIGNAL),表示后继存在等待结点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
可中断加锁
tryLock(long timeout, TimeUnit unit)
与Lock
的区别,在尝试加锁失败后,会将调用LockSupport.parkNanos(this, nanosTimeout)
将自己限时阻塞,若阻塞过程中被打断,则响应中断抛出异常。
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);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)