开篇
NonfairSync和FairSync相比而言,多了一次抢占机会,其他处理逻辑几乎是一模一样。
- NonfairSync的tryAcquire的操作流程中如果发现当前锁未被占用那么立即抢占锁。
- FairSync的tryAcquire的操作流程中如果发现当前锁未被还需要继续判断当前线程否是头结点才能发起锁抢占
java源码 - ReentrantLock
java源码 - ReentrantLock之FairSync
java源码 - ReentrantLock之NonfairSync
java源码 - ReentrantLock图解加锁过程
加锁过程
ReentrantLock的的锁过程如下:
- 1、先尝试获取锁,通过tryAcquire()实现。
- 2、获取锁失败后,线程被包装成Node对象后添加到CLH队列,通过addWaiter()实现。
- 3、添加CLH队列后,逐步的去执行CLH队列的线程,如果当前线程获取到了锁,则返回;否则,当前线程进行休眠,直到唤醒并重新获取锁了才返回。
public void lock() {
sync.lock();
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire的操作流程
- 1、第一步通过tryAcquire()尝试获取锁,成功则返回
- 2、获取锁失败后通过addWaiter添加到CLH队列的末尾
- 3、添加CLH队列后,通过acquireQueued()方法逐步的去执行CLH队列的线程,如果当前线程获取到了锁则返回;否则当前线程进行休眠,直到唤醒并重新获取锁后返回。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire的操作流程
- 1、如果锁未占用的情况下:当前线程直接抢占锁并设置锁占用线程为当前线程,非公平锁NonfairSync和FairSync的差别就在于这个地方,非公平锁直接抢占锁,而公平锁则需要判断是否位于头结点来决定是否抢占。
- 2、如果锁被占用的情况下:判断当前线程是否是占用锁线程,如果是则实现锁的可重入功能,设置锁占用次数。
- 3、如果上述全否那么就返回占锁失败的。
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;
}
addWaiter的操作流程
- 1、将当前线程包装成Node对象。
- 2、先尝试通过快速失败法尝试在CLH队尾插入Node对象
- 3、如果快速插入失败后那么就通过enq方法在CLH队尾插入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;
}
}
}
}
acquireQueued的操作流程
- 1、如果当前节点Node的前驱节点属于head,当前节点属于老二地位通过tryAcquire()尝试获取锁,获取成功后那么就释放原head节点(可以理解为head已经释放锁然后从CLH删除),把当前节点设置为head节点。
- 2、通过shouldParkAfterFailedAcquire()方法判断Node代表的线程是否进入waiting状态,直到被unpark()。
- 3、parkAndCheckInterrupt()方法将当前线程进入waiting状态。
- 4、休眠线程被唤醒的时候会执行 if (p == head && tryAcquire(arg))逻辑判断
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);
}
}
shouldParkAfterFailedAcquire的操作流程
- 1、如果前置节点处于SIGNAL状态,那么当前线程进入阻塞状态,返回true
- 2、如果前置节点处于ws>0也就是取消状态,那么当前线程节点就往前查找第一个状态处于ws<=0的节点
- 3、如果前置状态ws=0的节点,那么就把前置节点设置为SIGNAL状态
- 4、整个shouldParkAfterFailedAcquire函数是在for()循环当中循环执行的,我们可以想象按照步骤2->3->1的顺序执行,按照前置遍历寻找合适的前置节点,接着发现前置节点ws状态为0后重新设置为SIGNAL,最后发现前置节点状态为SINGAL后休眠线程自身。
- 5、线程从运行态进入waiting状态其实也是经历了一系列的处理过程的。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
解锁过程
release过程
- 1、通过tryRelease()方法尝试让当前线程释放锁对象
- 2、通过unparkSuccessor()方法设置当前节点状态ws=0并且唤醒CLH队列中的下一个等待线程
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease过程
- 1、如果占用锁线程非当前线程直接抛异常
- 2、递减锁计数后如果值为0那么就释放当前锁占用者
- 3、更新锁状态为未占用,即state为0
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;
}
unparkSuccessor过程
- 1、设置当前Node状态为0
- 2、寻找下一个等待线程节点来唤醒等待线程并通过LockSupport.unpark()唤醒线程
- 3、寻找下一个等待线程,如果当前Node的下一个节点符合状态就直接进行唤醒,否则从队尾开始进行倒序查找,找到最优先的线程进行唤醒。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
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);
}