前言
本篇是基于JDK8版本,分析ReentrantLock公平锁的获取和释放的源码。
1、获取公平锁
lock()方法在ReentrantLock.java的FairSync类中实现,它的源码如下:
final void lock() {
//ReentrantLock是可重入锁,所以单个线程每次进入都+1,初始值是0
acquire(1);
}
acquire()在AbstractQueuedSynchronizer(AQS)抽象类中定义的,它的源码如下:
public final void acquire(int arg) {
//这里总共有4个方法,tryAcquire实现方法在FairSync类中
//addWaiter、acquireQueued、selfInterrupt三个方法在AQS中实现
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.1、tryAcquire方法
tryAcquire方法作用就是尝试去获取锁,获取成功返回true,获取失败则返回false。
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁状态,就是每次线程进入都要加1的那个值
int c = getState();
//c等于0,就是初始状态,锁没有被占用
if (c == 0) {
//判断队列中当前线程是否是第一个
//CAS再次判断锁是否没有被占用
//设置锁被当前线程占用,然后返回
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果锁的拥有者是当前线程
//锁的状态+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//更新锁的状态,然后返回
setState(nextc);
return true;
}
return false;
}
1.2、addWaiter方法
addWaiter方法是创建当前线程的Node节点,并在Node中记录锁是“独占锁”类型,并且将该节点添加到队列的末尾,当前线程就处于等待状态了,我们先看看Node节点的源码:
private transient volatile Node head; // CLH队列的队首
private transient volatile Node tail; // CLH队列的队尾
// 队列的Node节点
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 线程已被取消,对应的waitStatus的值
static final int CANCELLED = 1;
// 当前线程的后一个线程需要被唤醒,对应的waitStatus值
static final int SIGNAL = -1;
// 线程休眠,对应的waitStatus的值
static final int CONDITION = -2;
// 其它线程获取到“共享锁”,对应的waitStatus的值
static final int PROPAGATE = -3;
volatile int waitStatus;
// 前一节点
volatile Node prev;
// 后一节点
volatile Node next;
// 节点所对应的线程
volatile Thread thread;
// 标记队列是独占锁还是共享锁
Node nextWaiter;
//共享锁则返回true,独占锁则返回false。
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回前一节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
看完Node源码,下面我们再看看addWaiter方法
private Node addWaiter(Node mode) {
//创建当前线程Node节点
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;
}
1.3、acquireQueued方法
acquireQueued方法是在队列中获取锁
final boolean acquireQueued(final Node node, int arg) {
//标记acquire是否成功
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,表示曾经中断过
interrupted = true;
}
} finally {
if (failed)
//如果tryAcquire出现异常那么取消当前结点的获取
cancelAcquire(node);
}
}
1.4、selfInterrupt方法
这个方法很简单,就是中断当前线程
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2、释放公平锁
public void unlock() {
//这里的1和获取锁的1是相同的,释放锁的时候是-1
sync.release(1);
}
2.1、release方法
public final boolean release(int arg) {
//tryRelease来尝试释放当前线程持有的锁。
//成功的话,则唤醒后继等待线程,并返回true。否则,直接返回false
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒当前线程的后继线程
unparkSuccessor(h);
return true;
}
return false;
}
2.2、unparkSuccessor方法
private void unparkSuccessor(Node node) {
//获取当前线程的状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//循环获取当前节点的后继节点状态小于等于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);
}
结束语
本篇详细的分析了ReentrantLock获取公平锁和释放公平锁的代码,大部分的代码上面都进行了中文注释。下一篇将介绍ReentrantLock的非公平锁的获取和释放。
分析源码不易,如果有帮助到你请随手点个赞,谢谢大家!