整体流程分析
今天我们通过源码来分析一下 ReentrantLock 的实现原理
1、利用AQS提供的 state 变量 通过对 state 的CAS 操作来标识线程是否获取到锁,将 state 由 0 -> 1 代表获取到锁,反之没有获取到锁,进而该线程进入竞争队列阻塞等待(双向链表实现的队列),如图下图1:
2、互斥锁又分为公平锁和非公平锁两种实现,那何为公平锁?何为非公平锁呢?
公平与非公平唯一的区别在于两者在获取锁的时候是否检查竞争队列是否有正在等待的线程,对于公平锁来说队列中有值时,线程直接进入队列阻塞等待,而非公平锁在获取锁时不会检查队列是否有值,上来先尝试去抢锁,抢不到在进入队列阻塞等待
3、互斥锁还提供了条件变量,在线程达到某个条件时让其等待在一个条件队列中等待其他线程对其唤醒,唤醒后会将其从条件队列中移除,转移到竞争队列中
下面我们来看看具体的源码实现(以非公平锁实例进行分析):
获取锁流程
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1)) // 直接 CAS state 抢锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 调用父类提供的统一模板方法获取锁
}
// 子类实现具体的获取锁的逻辑,模板方法设计模式
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) { // 当前 state == 0 表示无锁状态,直接CAS获取锁,多线程并发情况下可能会CAS失败
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {// 锁重入,对state进行++操作
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
获取锁方法实现:父类(AQS)提供的获取锁默认算法实现
// 已独占的模式获取锁,获取失败,进入队列阻塞,不响应中断
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
加入阻塞队列实现:通过以下源码我们发现是尾插法入队,并且优先关连prev指针(新节点的prev优先连上原tail指向的节点)(如下图2),延迟更新 tail 指针和 原tail节点的next指针。此步为何如此操作呢?为何是先更新prev而不是原tail节点的next呢?在后续释放锁的逻辑中我们会发现,线程在进行唤醒操作时,如果next节点为空将会从tail向前遍历找到队列中入队最早切未被取消的节点进行唤醒
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// tail不为空代表队列已经初始化,可以尝试将新节点挂入队列中(多线程并发场景下CAS可能失败),
// 如果此步入队失败将进入enq()方法循环处理,以保证线程成功入队阻塞
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) {
// 队列未初始化先进行队列初始化。为何先初始化 head 节点,而不是先初始化 tail?
// 如果先初始化tail节点,tail节点可见,head为空,如果此时持有锁的线程释放了锁,
// 需要唤醒等待的线程节点,此时它发现head为空,没有可唤醒的节点,
// 这会导致tail后的所有节点无法被唤醒(死了)
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
线程阻塞(进入OS)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果prev是头节点,此时可以再次尝试获取一次锁,若果获取锁成功,线程就无需进入OS睡眠等待了
// 次分支处理可以理解为优化操作,尽量减少线程进入OS,进入OS阻塞、唤醒、调度都是耗时的操作
if (p == head && tryAcquire(arg)) {
setHead(node);// 更新头节点(头节点就是当前持有锁的线程)
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 获取锁失败,进入OS阻塞更新节点状态
parkAndCheckInterrupt())// 进入OS阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // tryAcquire(arg) 异常时 取消该节点
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 状态已更新直接返回
return true;
if (ws > 0) { // waitStatus > 0 代表该节点已被取消,将其从队列中移除
// 向前遍历找到一个未被取消的节点,将当前节点连在其后面即可(如图3)
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前一个节点状态更新为SIGNAL状态,告诉前一个节点我是需要被唤醒的
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
取消节点流程
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// 从当前节点向前遍历将前面已取消的节点从队列中移除,然后将自己连在未取消的节点后面
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED; // 设置状态为取消状态
// 如果当前节点是尾节点,将自己从队列移除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
// 如果当前节点的next节点是正常节点,将其链到前一个未被取消的节点上
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果前面的正常节点是头节点,唤醒当前节点(已取消)的next节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);// 已唤醒后继线程
Node s = node.next;
// 此处的判断就是为了解决线程进入队列时为啥先关连prev指针,延后更新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); // 唤醒操作,进入线程调度队列等待CPU调度执行
}
释放锁流程
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) { // 子类实现具体逻辑
Node h = head;
// 如果 h == null 说明已被其他线程唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒
return true;
}
return false;
}
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;
}
Condition条件变量
条件队列是尾插头取,对条件变量的操作属于线程安全的,也即以下代码都是单线程操作
1、调用await()方法,将当前节点链在条件队列的尾部(采用单链表实现的队列)
2、入队成功后释放已获得的锁
3、进入OS阻塞
条件等待过程
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
// 不在竞争队列中,进入OS阻塞等待
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)// 检测是否发生中断
break;
}
// 条件唤醒后继续执行,竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); // 将取消的节点移除
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();// 将取消的节点移除
t = lastWaiter;
}
// 将节点链入条件等待队列
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
// 释放锁
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed) // 释放锁失败,去掉该节点
node.waitStatus = Node.CANCELLED;
}
}
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false; // 不在竞争队列
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
// 从竞争队列的尾部向前遍历查找,检查是否在竞争队列中
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
条件唤醒过程
唤醒过程需要将线程从条件队列转移到竞争队列,流程如下
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;// 拿到头节点
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null) // 头节点向后更新
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
// 将节点从条件队列转移到竞争队列
final boolean transferForSignal(Node node) {
// 更新节点状态
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 入队 返回的是竞争队列老的尾节点即 oldTail 节点(如图4)
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒当前线程让其去竞争锁
return true;
}