ReentrantLock非公平锁源码分析
ReentrantLock lock = new ReentrantLock();
lock.lock();
上面这段代码是最简单的获取ReentrantLock的非公平锁的代码,我们来看看这段代码后面的源码是如何运行的。
final void lock() {
//首先尝试看看能不能获取到锁,如果CAS成功,那么就获取到了
if (compareAndSetState(0, 1))
//如果获取到了,就记录一下当前的线程,以便后面的重入
setExclusiveOwnerThread(Thread.currentThread());
else
//如果CAS失败,则调用acurire方法
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
selfInterrupt();
}
这边的话首先会再尝试获取锁,如果获取到了,返回true,就会退出if语句,如果没有获取到,那么则将当前线程添加到队列中,并循环获取锁,直到获取到为止。
下面是tryAcquire方法的源码:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取到锁的标志状态
int c = getState();
if (c == 0) {
//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;
}
如果尝试获取锁失败了,则先会将当前线程添加到一个队列中:
private Node addWaiter(Node mode) {
//使用当前线程创建一个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
//如果尾节点还是空的,那么构建一个空节点做为头节点
//然后在下一次循环的时候进入到else
if (compareAndSetHead(new Node()))
tail = head;
} else {
//和上面一样,将当前线程构建的节点添加到队列的尾部
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
到这里,队列已经构建完成了,那么这个队列中的节点在有锁释放后,又是怎么去争夺锁资源的呢?
final boolean acquireQueued(final Node node, int arg) {、
//标识获取锁的过程中是否出现了异常
boolean failed = true;
try {
//标识线程在等待唤醒的时候是否被打断(interrupt)
boolean interrupted = false;
//这里会循环获取锁,知道获取到或者出现异常
for (;;) {
final Node p = node.predecessor();
//如果当前线程为第一个节点(排除head节点),则尝试获取锁
if (p == head && tryAcquire(arg)) {
//如果获取到了,则将当前节点设置为head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断节点是否需要park,如果需要,则进入park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这段代码有一点比较难理解,就是首先会判断当前线程的前一个节点是否为head节点,如果是的话,才会尝试获取锁。但是非公平锁不是应该所有的节点都去争夺锁资源吗?其实非公平是作用于新入的节点,而已经调用过addWaiter方法的节点,则需要排队。
PS:park方法与wait方法的作用类似。
下面我们再来看一下ReentrantLock是如何来判断当前线程获取锁失败后是否需要进入到park状态。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前节点是SIGNAL状体,则代表需要park
return true;
if (ws > 0) {
//如果waitStatus的值大于0,代表已取消,需要将无效的节点删除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//否则将前节点设置为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
上面的代码很好理解,但是需要知道Node的waitStatus的几种状态的含义。
- CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
- SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
- CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
- PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。
- 0状态:值为0,代表初始化状态。
下面再来看看parkAndCheckInterrupt方法:
private final boolean parkAndCheckInterrupt() {
//通过LockSupport类来park该线程
LockSupport.park(this);
//将park的线程唤醒可能是调用unpark方法,也可能是被打断了
return Thread.interrupted();
}
到这里,非公平锁的整个获取流程就结束了。
ReentrantLock非公平锁实现原理小结
通过上面的源码解析,我们已经能够大致了解ReentrantLock是如何实现非公平锁的了,下面我们来个小结。
这里有一点需要注意,就是在最后的时候,如果线程获取到了锁,并且中途被打断过,这个时候线程会调用interrupte(),但是这个是没有任何意义的,因为运行中的线程是不能被打断的。
当然,ReentrantLock也是支持被打断的,具体可参考:
ReentrantLock公平锁源码解析
基于上面的内容,我们已经理解了ReentrantLock非公平锁的实现原理,其实公平锁和非公平锁的源码差不多,只有两处不一样。
第一处:
final void lock() {
acquire(1);
}
这里就是调用lock方法的入口,和非公平锁不一样的是非公平锁会先去尝试通过CAS获取锁,但是公平锁不会。
第二处:
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;
}
这段代码是尝试去获取锁,可以看出,这边当state为0的时候,也就是无锁状态的时候,会先去判断当前线程的是否构建了节点且节点是否为head后的第一个节点,如果是,才会去尝试CAS获取锁。
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//只有当没有任何节点获取锁或者本节点为head后第一个节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从这里我们也能看出公平锁和非公平锁的区别:公平锁的新入线程不能直接获取锁,必须去排队(除非没有任何竞争发生)。而非公平锁新入的线程则可以先尝试获取锁,如果失败了再排队。