前言
JUC(java.util.concurrent)包下的很多并发同步工具类,大多基于AQS(AbstractQueuedSynchronizer 抽象队列同步器)实现加解锁的逻辑,其中acquire()尤为重要。
① acquire()是独占式地获取资源,acquireShared()是共享式地获取资源,如ReentrantReadWriteLock的writeLock和readLock;
② AQS采用模板方法模式实现acquire(),封装了线程获取资源失败后,进入同步队列并阻塞的逻辑。
三个核心方法
1. tryAcquire(int arg)
2. addWaiter()
3. acquireQueued(final Node node, int arg)
public final void acquire(int arg) {
//尝试获取资源失败,且成功加入同步队列,则阻塞线程
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
selfInterrupt();
}
1. tryAcquire(int arg)
① 尝试获取资源,留给子类重写,否则会抛出异常;
② 尝试成功则修改资源状态及拥有者,尝试失败则返回false。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
2. addWaiter()
① 将当前线程封装成Node节点,并添加到同步队列尾部;
② 队列未初始化时,pred和tail都为null,tail为null时执行enq();
③ compareAndSetTail(pred, node)是采用cas线程安全地设置队列尾节点。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
2.1 enq(final Node node)
① 通过cas自旋的方式将节点添加到队列尾部;
② tail为null表示同步队列未初始化,即没有虚拟head节点;
③ 所以在没有虚拟节点的情况下,先走if逻辑添加虚拟节点,再走else逻辑的。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
3. acquireQueued(final Node node, int 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);
}
}
3.1 shouldParkAfterFailedAcquire(Node pred, Node node)
① 判断前节点的ws是否大于0,大于0表示节点被取消(因中断等异常原因)
② 如果前节点被取消,则一直往前找,直到找到不被取消的节点
③ 将前节点的ws设置为SIGNAL,表示下一个节点需要被唤醒
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;
}
3.2 parkAndCheckInterrupt()
① LockSupport调用UNSAFE类的unpark()阻塞当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
总结
并发工具类调用lock()方法进行加锁操作时,底层采用了acquire()进行资源获取。首先,会尝试获取资源(交给子类实现),获取资源失败则封装成Node节点,放入同步队列中并阻塞挂起,同时检测是否中断,中断则取消排队。