NonfairSync
重入锁中的非公平锁,尝试获取锁的线程有可能会成功,如果不成功的话,则会进入AQS的队列中。
NonfairSync加锁流程
-
ReentrantLock.lock函数中,会调用到NonfairSync.lock方法,首先会通过CAS方法,尝试将当前的AQS中的
State
字段改成从0改成1,如果修改成功的话,说明原来的状态是0,并没有线程占用锁,而且成功的获取了锁,只需要调用setExclusiveOwnerThread
函将当前线程设置成持有锁的线程即可。否则,CAS操作失败之后,和普通锁一样,调用acquire(1)
函数尝试获取锁。/** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
-
而在
acquire(1)
函数中,会判断tryAcquire(1)
以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,如果尝试获取失败并且添加队列成功的话,那么就会调用selfInterrupt
函数中断线程执行,说明已经加入到了AQS的队列中。
在NonfairSync的tryAcquire
中,会调用到nonfairTryAcquire
函数。
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
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;
}
在nonfairTryAcquire函数中,会尝试让当前线程去获取锁:
- 获取当前线程,以及AQS的状态
- 如果当前AQS的状态为0的话,那么说明当前的锁没有被任何线程获取,则尝试做一次CAS操作,将当前的状态设置成
acquires
,如果设置成功了的话,那么则将当前线程设置成锁持有的线程,并且返回true,表示获取成功。 - 如果当前的状态不为0的话,说明已经有线程持有锁,则判断当前线程与持有锁的线程是否相同,如果相同的话,则将当前的状态加上acquires重新将状态设置,并且返回true,这也就是重入锁的原因。
- 如果当前线程没有获取到锁的话,那么就会返回false,表示获取锁失败
而在addWaiter
方法则会新建一个Node,然后将节点添加到队列中,让这个节点成为 tail。
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;
}
- 根据Mode新建一个Node对象,而在上面传进来的Mode则是
Node.EXCLUSIVE
,然后得到尾节点tail
,判断当前的尾节点是否为空,如果尾节点不为空的话,那么则将当前节点的prev
设置成tail,也就是将自己作为尾节点添加 - 然后通过CAS操作,判断尾节点是否有修改过,如果当前的尾节点没有变化过,并且将
node
成功设置成尾节点的话,那么则将之前尾节点的next
设置成当前的node
,并且返回尾节点node
- 否则调用
enq(node)
将当前节点添加到队列尾部,并且返回node,而在enq
方法中,则会判断头节点和尾节点是否初始化,如果没有初始化则会初始化,然后通过自旋的方式,将tail的next设置成node,并且将node的prev设置成tail,然后将node设置成tail。以达到将node设置成尾节点的目的。
接着调用acquireQueued
函数,传入尾节点。
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);
}
}
在acquireQueued()中,当前线程会等待它在“CLH队列”中前面的所有线程执行并释放锁之后,才能获取锁并返回。如果“当前线程”在休眠等待过程中被中断过,则调用selfInterrupt()来自己产生一个中断。
- 进入这个方法后,可以看到会进入一个死循环,这个死循环中,只有当
p == head && tryAcquire(arg)
才会返回,这个条件代表的是,只有当当前节点的前驱是头节点,并且已经成功获取锁了,才会将当前的节点设置成头节点,并且前节点的next设置成空帮助GC回收,并且将failed标记成失败,并且返回当前线程是否被中断了。 - 如果当前节点的前驱不是头节点的话,那么则会判断当前线程在获取锁失败后, 是否需要阻塞,如果需要阻塞的话,就会调用
parkAndCheckInterrupt
方法进行当前线程的阻塞,并且在线程唤醒后,返回是否当前线程已经中断。
而在sholdParkAfterFailedAcquire
函数中,会判断当前节点的前驱的状态,如果当前前驱的状态为Node.SIGNAL
的话,那么说明,当前持有锁的线程正在阻塞,需要等它释放了锁之后才能获取,所以返回true,表示需要等待锁的释放,阻塞请求线程。否则,如果持有锁的线程的状态>0的话,说明前驱节点已经处于CANCEL
状态,那么就会进入一个循环,直到找到一个状态小于0的(也就是SINGAL,CONDITION,PROPAGETE
)状态的节点,然后把该节点的next设置成当前节点,中间的那些CANCEL
节点就都被抛弃掉了,如果是PROPAGETE
状态的话,那么说明需要一个信号,但是先不阻塞当前线程,调用者会继续尝试获取锁,于是就通过一个CAS操作,将前驱节点的waitStatus
设置成Node.SIGNAL
,并且告知当前线程不用阻塞。
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;
}
网友评论