前边一篇文章分析了ReentrantLock通过FairSync实现的加锁,现在继续分析解锁的源码。
public void unlock() {
sync.release(1);
}
// 这个是AbstractQueuedSynchronizer的方法
public final boolean release(int arg) {
// 首先看tryRelease方法。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 获取一下当前锁的状态,然后减去1
int c = getState() - releases;
// 当前线程必须持有锁,才可以解锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果等于0了,则解锁成功,重置锁,并设置排它锁持有为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
public final boolean release(int arg) {
// 接着看,释放锁成功了。则去唤醒阻塞队列的第一个节点。
if (tryRelease(arg)) {
// 先把head节点拿到。
Node h = head;
// head节点不能为空(当前线程就是属于head,并且head的waitStatus被它的下一节点修改为-1了的。如果这个地方有疑问的可以看--# [AbstractQueuedSynchronizer源码分析(一)---加锁](https://juejin.cn/post/7216299104404946999))
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 此时的参数是head节点
return true;
}
return false;
}
// Node参数是head节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
// 修改head的waitStaus=0,此时是-1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// s就是阻塞队列的第一个节点
Node s = node.next;
// 阻塞队列为空或者第一个节点取消了排队
if (s == null || s.waitStatus > 0) {
s = null;
// 为什么从最后一个节点往前遍历,寻找下一个节点?是因为s=null?不是很确定。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);// 唤醒下一个Node。
}
接下来就是线程从加锁被挂起的地方重新开始执行了
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);// 唤醒线程
return Thread.interrupted();// 判断是否被中断了。false
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 重新进入循环,p是等于head的,然后尝试加锁,成功
if (p == head && tryAcquire(arg)) {
setHead(node);//设置当前节点为head,返回
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//1. parkAndCheckInterrupt()方法是返回false的,if判断不会进去,然后重新循环
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 正常情况不会执行
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// acquireQueued方法返回了false,获取锁成功了,线程继续执行自己的业务代码。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
顺道分析一下非公平锁的加锁源码吧。
final void lock() {
// 由于非公平锁不需要按照队列一个个执行,所以进来就先判断是否可以后去所,如果成功了,则万事大吉。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取锁失败了。
acquire(1);
}
public final void acquire(int arg) {
还是先try一下吧。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
// 此时调用的就是非公平锁的方法了.
return nonfairTryAcquire(acquires);
}
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;
}
public final void acquire(int arg) {
// 如果获取锁失败了,还是老老实实的去排队去吧。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
再来看看非公平锁的解锁代码又是怎么样操作的?和公平锁一样了。
public final boolean release(int arg) {
// 任然是先去解锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 然后去唤醒下一个节点。
unparkSuccessor(h);
return true;
}
return false;
}
总结:看了源码就会发现,非公平锁和公平锁只有在lock的时候有一点点区别,非公平锁在lock的时候,会先去抢一下锁,如果没有抢到,才会进入acquire(1);,在进入这个方法了,如果发现了锁释放了,还是会直接抢锁,而不去判断阻塞队列里边是否有线程等着了。如果这里也还是没有抢到锁,那剩下的就和公平锁一样了。