AbstractQueuedSynchronizer源码分析(二):独占锁的获取与释放
1、典型实现:ReentrantLock
ReentrantLock就是一个典型的不响应中断的独占锁,那就从ReentrantLock的lcok()开始走读不响应中断的独占锁的实现逻辑。
这里我们基于ReentrantLock默认的非公平锁的lock(),公平锁我们留待分析ReentrantLock的源码中进行详细分析。
先上NonfairSync代码
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 立即尝试获取锁,失败后再去走正常的锁获取流程。
* (非公平不需要根据队列顺序来获取锁,直接尝试获取锁可以很高的提升锁的效率)
*/
final void lock() {
//0表示还未有线程获得锁
if (compareAndSetState(0, 1))
//设置独占锁的拥有者
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
NonfairSync的类关系是NonfairSync extends Sync,Sync extends AbstractQueuedSynchronizer。这里采用的模板模式进行设计的,AbstractQueuedSynchronizer的acquire(int arg)方法已经完成了不响应中断的独占锁获取逻辑,而tryAcquire(int acquires)是对state锁标识位的管理,需要子类实现。
2、AbstactQueuedSynchronizer不响应中断的独占锁方法介绍
AbstactQueuedSynchronizer中供子类实现的方法
方法名 | 作用 |
---|---|
boolean tryAcquire(int arg) | 尝试以独占模式获取。此方法应该查询对象的state是否允许以独占模式获取它,如果允许,那么可以获取它。 |
boolean tryRelease(int arg) | 尝试以独占模式设置state来反映对锁的释放。 |
boolean isHeldExclusively() | 同步器是否在独占模式下被当前线程占用。 |
AbstactQueuedSynchronizer中实现的方法
方法名 | 作用 |
---|---|
void acquire(int arg) | 在独占模式下获取锁,不响应中断 |
Node addWaiter(Node mode) | 以mode模式为当前线程创建node节点,并且加入CLH队列中。 |
boolean compareAndSetTail(Node expect, Node update) | CAS更新tail属性 |
Node enq(final Node node) | 将node节点加入队尾,若队列未初始化,先进行初始化,在同步器整个生命周期中只会初始化一次 |
boolean compareAndSetHead(Node update) | CAS更新head属性 |
boolean acquireQueued(final Node node, int arg) | 以独占不响应中断模式为已在队列中的线程获取锁 |
void setHead(Node node) | 把node设为CLH队列的head节点,并将node节点thread释放、把原head节点从CLH队列中释放 |
boolean shouldParkAfterFailedAcquire(Node pred, Node node) | 寻找安全点,当找到安全点后进行阻塞 |
void LockSupport.park(Object blocker); | 当前线程阻塞 |
boolean compareAndSetWaitStatus(Node node,int expect,int update) | CAS更新waitStatus属性 |
boolean parkAndCheckInterrupt() | 阻塞当前线程,并在唤醒后返回中断状态 |
void cancelAcquire(Node node) | 取消线程获取锁 |
boolean compareAndSetNext(Node node,Node expect,Node update) | CAS更新next属性 |
void unparkSuccessor(Node node) | 唤醒后继节点中的线程 |
boolean compareAndSetState(int expect, int update) | CAS更新state属性 |
void selfInterrupt() | 标记当前线程中断状态 |
- CAS方法介绍
在上面的方法中,我们发现#compareAndSetTail,#compareAndSetHead,#compareAndSetWaitStatus,#compareAndSetNext,#compareAndSetState方法都是CAS方法。拉出源码一看,方法体内部是通过调用unsafe#compareAndSwapObject方法属性的更新。
关于CAS的介绍,可以参考【Java中的CAS应用】
下面源码中,展示了AQS那些属性可以进行CAS操作。
private static final Unsafe unsafe = Unsafe.getUnsafe();
// AbstractQueuedSynchronizer.class的属性
// state属性下标,以下雷同
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
// Node.class的属性
// waitStatus属性下标,以下雷同
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
// 获取属性state在AbstractQueuedSynchronizer类中的下标,以下雷同
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
- acquire(int arg)方法源码及注释
// 在独占模式下获取锁,忽略中断;参数arg传输给#tryAcquire方法,可以表示任何你想要表达的意思。
public final void acquire(int arg) {
// 1、tryAcquire(arg),调用同步器实现的tryAcquire获取锁,返回true流程结束,否则进入2
// 2、addWaiter(Node.EXCLUSIVE),将当前线程封装成node节点,加入到CLH队列尾部,进入3
// 3、若线程从queued中被唤醒时,返回中断状态,若为true,进入4
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 4、将中断状态传输给当前线程
selfInterrupt();
}
/**
* 这里的tryAcquire需要AQS的子类(同步器)进行实现,提供给父类的模板方法调用。
* return 返回true表示获取锁成功,否则表示获取锁失败
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
- addWaiter(Node mode) 方法源码及注释
/**
* 以mode模式为当前线程创建node节点,并且加入CLH队列中。
*
* @param mode Node.EXCLUSIVE 独占模式, Node.SHARED 共享模式
* @return 创建的node节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// node节点尝试快速入队,若失败进入enq()方法
Node pred = tail;
if (pred != null) {
// 设置node节点的前驱,无多线程并发,放在CAS外
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 存在多个线程并发操作pred节点,必须在节点入队成功后,把node设为pred节点的后继,。
// 必须要CAS成功后才能操作。
pred.next = node;
return node;
}
}
// node节点入队
enq(node);
return node;
}
- enq(final Node node)方法源码及注释
/**
* 将node节点加入队尾,若队列未初始化,先进行初始化,在同步器的整个生命周期中只会初始化一次
* @param node 加入队尾的node节点
* @return node节点的前驱节点
*/
private Node enq(final Node node) {
// 通过循环,不断尝试将node加入队尾;直至成功,跳出循环。
for (;;) {
Node t = tail;
if (t == null) {
// 初始化队列,Node节点中不包含线程
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// 加入队尾成功,跳出循环。
t.next = node;
return t;
}
}
}
}
- acquireQueued(final Node node, int arg)源码及注释
/**
* 以独占不响应中断模式获取已在队列中的线程,直至tryAcquire(arg)方法返回true(获取锁成功),并返回中断状态。
*
* @param node 当前线程的node节点
* @param arg acquire方法参数
* @return 如果等待时被中断,返回true,否则返回false
*/
final boolean acquireQueued(final Node node, int arg) {
// 失败状态
boolean failed = true;
try {
// 中断状态
boolean interrupted = false;
// 进行循环,直至tryAcquire成功后,返回中断状态。
for (;;) {
// 获取前驱节点
final Node p = node.predecessor();
// 1、前驱节点为head && tryAcquire获取锁返回true,返回中断状态,否则进入2
if (p == head && tryAcquire(arg)) {
// 将该节点设为head节点,并将node节点中的线程释放
setHead(node);
// 释放前驱节点的引用,帮助GC
p.next = null;
// 标记失败状态
failed = false;
return interrupted;
}
// 2、shouldParkAfterFailedAcquire,判断node节点是否应该阻塞,返回true进入3,否则进入1
// 3、parkAndCheckInterrupt,node节点进入阻塞状态,被唤醒后返回中断状态,返回true,标记interrupted为true,否则不标记;随后进入1
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 判断失败状态,若为true;取消node节点acquire。根据上面流程看,个人认为只有在出现异常时才会出现cancelAcquire(node)的情况。
if (failed)
cancelAcquire(node);
}
}
- setHead(Node node)源码及注释
/**
*把node设为CLH队列的head节点,并从CLH队列中出列。
*
*/
private void setHead(Node node) {
// 设为head节点
head = node;
// 释放node节点中线程
node.thread = null;
// 释放对前驱节点的引用,帮助GC
node.prev = null;
}
- shouldParkAfterFailedAcquire(Node pred, Node node)源码及注释
/**
* 获取锁失败后,根据前驱判断当前节点是否应该进行阻塞。
*
* @param pred 前驱节点
* @param node 当前节点
* @return 返回true,当前线程应该阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取pred节点waitStatus状态值
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.
*/
/*
* 若pred节点为SIGNAL状态,表示pred节点释放锁时会唤醒(unpark)后继节点;同时也表示node节点可以安全的进行阻塞(park)了。
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
/*
* waitStatus>0表示前驱节点被取消,放弃当前的pred节点,不断循环寻找前驱节点,直至寻找到一个未被取消的节点。
*/
do {
// 1、pred = pred.prev,获取pred的前驱节点
// 2、node.prev = pred.prev,当前节点指向新的前驱节点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 寻找成功后,前驱节点next引用node节点
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.
*/
/*
* 上面两种状态都不满足时,通过CAS操作更新pred节点为waitStatus=SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 不能park,需要不断遍历,直到前驱节点waitStatus=SIGNAL
return false;
}
- LockSupport.park(Object blocker)源码
具体关于LockSupport的内容,可以参考【LockSupport的使用】,不做赘述。
//这行代码的作用就是使线程阻塞在这里,等待其他的线程调用该unpark该线程,唤醒线程后继续执行后面方法
LockSupport.park(this);
- parkAndCheckInterrupt()源码及注释
/**
* 现在阻塞在当前,当前驱线程调用unpark方法后,可以唤醒阻塞,并返回中断状态。
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- cancelAcquire(Node node)源码及注释
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
/**
* 取消node节点,
* 若node节点的prev是正常节点,连接node.prev和node.next
* 若node节点的prev是非正常状态的节点,就唤醒node节点的后继节点
*/
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
// node不存在,忽略后面逻辑
if (node == null)
return;
// 释放node节点中线程
node.thread = null;
// Skip cancelled predecessors
// 跳过所有被取消的前驱
Node pred = node.prev;
while (pred.waitStatus > 0)
// 1.pred = pred.prev 将pred的前驱节点设为pred
// 2.node.prev = pred 将pred设置为node的前置节点
// 这里的逻辑等于是放弃原来的pred节点,将pred.prev设为新的pred
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
// 取出pred前驱原值
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// 设置node节点状态为CANCELLED
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
// 如果node为tail节点,就将pred设为tail节点
if (node == tail && compareAndSetTail(node, pred)) {
// 释放pred节点的next属性
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
// 若node后继节点需要唤醒,尝试将pred的next属性设为后继节点。否则就唤醒node节点
int ws;
// 1.pred节点不为head节点,ture进入2,否则进入6
// 2.pred节点waitStatus为SIGNAL,true进入4,否则进入3
// 3.当pred未被取消,并将其waitStatus更新为SIGNAL成功后进入4,否则进入6
// 4.若pred的线程不为空进入5,否则进入6
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 5.若node.next节点是正常节点,将node.next节点地址指向pred.next地址,等于放弃了node节点了。
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 6若pred节点不满足上面的情况,就需要直接唤醒node的后继节点,即node.next
unparkSuccessor(node);
}
// 帮助GC
node.next = node; // help GC
}
}
- unparkSuccessor(Node node)源码及注释
/**
* 唤醒node的后继节点
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 状态为负,尝试清除状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 若node.next已经被取消,那需要重tail变量找到离node最近的那个节点,作为node节点后继节点唤醒
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒node的后继节点
if (s != null)
LockSupport.unpark(s.thread);
}
- selfInterrupt()源码及注释
/**
* Convenience method to interrupt current thread.
*/
/**
* 将中断状态传输给当前线程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
上面代码走读添加了很多注释,能够清晰的理解到不响应中断锁的获取方式。以上的注释和理解的
篇幅限制,相关的知识点就贴上相关链接了,也是我在走读代码时遇到的不懂,然后去了解的。若读者朋友们对其不是很熟悉,也可以先阅读相关文章,主要是【CAS】和【LockSupport】。
不响应中断的获取独占锁的流程图
附上不响应中断的获取独占锁的流程图
3、AbstactQueuedSynchronizer响应中断的独占锁方法
在介绍响应中断的独占锁之前,需要了解什么是【中断】,在了解中断后。我们继续看acquireInterruptibly(int arg) throws InterruptedException方法,对比acquire(int arg)发现,acquireInterruptibly方法在获取到线程中断标记后,立即抛出InterruptedException异常以做响应。
方法名 | 作用 |
---|---|
void acquireInterruptibly(int arg) throws InterruptedException | 尝试回去哦响应中断的独占锁 |
doAcquireInterruptibly(int arg) throws InterruptedException | 将线程加入CLH队列中进行管理 |
与不响应中断锁不同主要是以上两个方法,其他相同方法不在此赘述。
- void acquireInterruptibly(int arg) throws InterruptedException 若线程被标记中断,直接抛出中断异常
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 线程中断立即响应
if (Thread.interrupted())
throw new InterruptedException();
// 线程获取独占锁失败后,进行入队操作
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
- void doAcquireInterruptibly(int arg) throws InterruptedException 若线程被标记中断,直接抛出中断异常
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// 入队
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 直至获取到锁或线程被标记中断
for (;;) {
// head节点(dummy)的next可以尝试获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// node成为新的head节点
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 1、寻找安全点,成功后进入2,否则再次进入循环
// 2、进入阻塞,阻塞被唤醒后返回中断状态,若被标记中断,进入3,否则再次进入循环
// 3、抛出中断异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 抛出中断异常后,取消node节点
if (failed)
cancelAcquire(node);
}
}
4、AbstactQueuedSynchronizer独占锁的释放
方法名 | 作用 |
---|---|
boolean release(int arg) | 释放独占模式下的锁 |
void unparkSuccessor(Node node) | 唤醒后继节点中的线程 |
- release(int arg)源码及注释
public final boolean release(int arg) {
// 调用子类实现tryRelease方法,返回ture表示release成功,否则结束流程
if (tryRelease(arg)) {
// 释放锁成功,唤醒队列中第一个node节点中的线程,因为head节点是dummy节点,所以唤醒head.next节点中线程。
Node h = head;
// 此时的head节点的waitStatus应该为-1,是在调用acquire时设置的。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
5、总结
从源码的走读和源码注解理解,了解到AbstactQueuedSynchronizer独占锁如何管理线程,如何阻塞,阻塞在何处,何时被唤醒,响应中断和不响应中断如何管理中断状态。定义了从尝试获取–阻塞–唤醒–尝试获取的整个流程。另外也知道锁的状态(state)管理完全是依赖子类对tryAcquire(int arg)和tryRelease(int arg)的实现。换句话说锁状态(state)是交由子类实现管理,AQS并不关心锁的管理,它只关心Thead何时如何怎样去获取锁。
下一章节介绍共享锁,请各位读者持续关注。