07-多线程笔记-2-锁-3-Lock-1-AQS
AbstractQueuedSynchronizer简称AQS,它是java.util.concurrent包下CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore实现的基础。
AQS通过内部实现的FIFO等待队列来完成资源获取线程的等待工作,如果当前线程获取资源失败,AQS则会将当前线程以及等待状态等信息构造成一个Node结构的节点,并将其加入等待队列中,同时会阻塞当前线程;当其它获取到资源的线程释放持有的资源时,则会把等待队列节点中的线程唤醒,使其再次尝试获取对应资源。
AQS是一个抽象类,当我们继承AQS去实现自己的同步器时,要做的仅仅是根据自己同步器需要满足的性质实现线程获取和释放资源的方式(修改同步状态变量的方式)即可,至于具体线程等待队列的维护(如获取资源失败入队、唤醒出队、以及线程在队列中行为的管理等),AQS在其顶层已经帮我们实现好了,AQS的这种设计使用的正是模板方法模式。
参考JDK11
AbstractOwnableSynchronizer
线程专有同步器,此类为创建锁和可能需要所有权概念的相关同步器提供了基础。这个类是
AbstractQueuedSynchronizer
的父类;
此类只有一个变量
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
和对应的get/set函数;在java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryRelease
、java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
等函数中用于制定拥有锁的线程;
实例属性
AQS使用一个int成员变量state去表征当前资源的同步状态。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
private volatile int state;
AQS持有head指针和tail指针,头结点是抢占锁成功而持有锁的线程对应的结点,若有线程抢锁失败,AQS会创建新结点并用CAS操作使其成为新的尾结点
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
Node
AQS通过维护一个等待获取锁的线程队列来管理(获取资源失败入队/唤醒出队)抢占锁的线程,这个队列是一种 CLH介绍的变体。
AQS把对某线程的一些控制信息放到了其前驱中维护,当某结点的前驱释放锁或被取消时会唤醒其后继,而其后继会在获取锁成功后将自己设为新的头结点,AQS对这个维护等待线程队列的操作都是非阻塞的,也是线程安全的。队列中的每个结点都是类Node的一个实例。
共享
AQS支持线程抢占两种锁——独占锁和共享锁:
- 独占锁:同一个时刻只能被一个线程占有,如ReentrantLock,ReentrantWriteLock等;
- 共享锁:同一时间点可以被多个线程同时占有,如ReentrantReadLock,Semaphore等;
在Node类中,有两个属性用于标识当前线程请求的是独占线程还是共享线程;
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
等待状态
Node节点中,维护
waitStatus
属性标识线程的等待状态;waitStatus
初始默认为0,Condition队列中初始默认为-2;它有以下几种取值:
//结点已被取消,表示线程放弃抢锁,结点状态以后不再变直到GC回收它
static final int CANCELLED = 1;
//结点的后继已经或很快就阻塞,在结点释放锁或被取消时要唤醒其后面第1个非CANCELLED结点
static final int SIGNAL = -1;
/** Condition队列中结点的状态,CLH队列中结点没有该状态,当Condition的signal方法被调用,
Condition队列中的结点被转移进CLH队列并且状态变为0 **/
static final int CONDITION = -2;
//与共享模式相关,当线程以共享模式去获取或释放锁时,对后续线程的释放动作需要不断往后传播
static final int PROGAGATE = -3;
0:新结点入队时的默认状态。
负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
模板方法
AQS提供了针对锁操作的一些目标方法,这些方法需要在子类中实现,像
tryReleaseShared/tryAcquireShared,tryAcquire/tryRelease
,
独占锁
获取
如果成功获取锁,则返回;如果获取锁失败,则将将当前线程加入到等待队列中,并在队列中执行自旋获取锁,直到成功获取锁或者线程满足阻塞条件而阻塞;当前线程只有被前一个有效等待节点记录后才能进入阻塞状态。前一个有效节点释放锁后,会通知处于阻塞状态的当前线程,然后当前线程重新自旋获取锁;
如果在获取锁的过程中出现异常,会将当前节点从等待队列中删除。
acquire(int arg)方法是独占模式下线程获取共享资源的顶层入口。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
函数流程如下:
(1)tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
(2)addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
(3)acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
(4)如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
加入CLH队列的过程不再描述,主要对
acquireQueued()
进行说明
-
acquireQueued(Node, int)
/** * Acquires in exclusive uninterruptible mode for thread already in * queue. Used by condition wait methods as well as acquire. * * @param node the node * @param arg the acquire argument * @return {@code true} if interrupted while waiting */ final boolean acquireQueued(final Node node, int arg) { // 当前线程是否被中断 boolean interrupted = false; try { // 自旋获取资源 for (;;) { // 获取当前线程对应节点的前驱结点 final Node p = node.predecessor(); // 如果前驱结点是头节点,当前线程有获取资源的资格,然后获取资源 if (p == head && tryAcquire(arg)) { // 如果获取资源成功,修改队列头指针指向自己 setHead(node); // 释放当前节点的头节点示例 p.next = null; // help GC // 返回中断状态 return interrupted; } // 没有获取资源资格,或者获取资源失败,需要执行shouldParkAfterFailedAcquire方法,用于确定当前结点对应的线程是否可以进入阻塞状态 if (shouldParkAfterFailedAcquire(p, node)) // 如果可以进入阻塞状态,执行parkAndCheckInterrupt方法以阻塞当前线程 interrupted |= parkAndCheckInterrupt(); } // 如果执行tryAcquire失败,取消失败节点 } catch (Throwable t) { // 删除当前节点 cancelAcquire(node); if (interrupted) selfInterrupt(); throw t; } }
释放
释放锁分两步,第一步释放资源,第二步唤醒当前节点下一个可用的节点对应的线程;
共享锁
获取
共享资源的获取,获取成功则直接返回,获取失败则进入等待队列,直到获取到资源为止,整个过程忽略中断。
public final void acquireShared(int arg) {
// 如果成功获取共享锁,直接返回;否则,进入等待队列
if (tryAcquireShared(arg) < 0)
// 进入等待队列,自旋获取锁或阻塞
doAcquireShared(arg);
}
/**
* Acquires in shared uninterruptible mode.
* @param arg the acquire argument
*/
private void doAcquireShared(int arg) {
// 加入到等待队列
final Node node = addWaiter(Node.SHARED);
// 是否中断标识,此处也是在获取锁结束后响应中断,只有在获取中断锁(acquireSharedInterruptibly)时,才会在获取锁的过程中响应中断
boolean interrupted = false;
try {
for (;;) {
// 获取前继节点
final Node p = node.predecessor();
// 如果前继节点为头节点(成功获取了锁的节点),则此阶段有资格获取锁
if (p == head) {
//尝试获取锁
int r = tryAcquireShared(arg);
// 获取锁成功
if (r >= 0) {
// 成功获取锁后,将自身设置为等待队列的头节点(之前的头节点会出队,被GC回收),并唤醒所有后续的共享锁申请节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
// 如果没有获取锁资格,判断是否可以进入阻塞状态;如果可以,线程进入阻塞状态
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
// 如果请求共享锁发生异常,将当前节点从等待队列中删除,取消申请锁流程
cancelAcquire(node);
throw t;
} finally {
// 响应中断
if (interrupted)
selfInterrupt();
}
}
/**
* Sets head of queue, and checks if successor may be waiting
* in shared mode, if so propagating if either propagate > 0 or
* PROPAGATE status was set.
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 修改等待队列的头节点,之前的头节点出队,等待GC回收
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
// 如果资源有剩余量,并且头节点等待状态小于0(小于0表示头节点后续有阻塞节点),则继续处理后续节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果后续节点是申请共享锁的,则唤醒后续节点
if (s == null || s.isShared())
doReleaseShared();
}
}
/**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
//记录当前头节点,后面会唤醒后继结点,如果后继结点成功获取共享锁,会修改等待队列的头节点;
Node h = head;
//
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果节点等待状态为SIGNAL,后续肯定有阻塞节点,则唤起后续节点
if (ws == Node.SIGNAL) {
// 将当前节点的等待状态置为0(初始化状态)
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后续节点
unparkSuccessor(h);
}
// 如果当前节点的等待状态为0(节点初始化,无后续节点),将节点状态修改为PRORAGATE,表示一旦出现一个新的共享结点连接在该结点后,该结点的共享锁将传播下去。
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果后继结点未能成功获取共享锁(获取成功后会修改头节点),结束循环
if (h == head) // loop if head changed
break;
}
}
释放
共享锁的释放后,会唤醒后续节点。
public final void acquireShared(int arg) {
// 如果成功获取共享锁,直接返回;否则,进入等待队列
if (tryAcquireShared(arg) < 0)
// 进入等待队列,自旋获取锁或阻塞
doAcquireShared(arg);
}
/**
* Acquires in shared uninterruptible mode.
* @param arg the acquire argument
*/
private void doAcquireShared(int arg) {
// 加入到等待队列
final Node node = addWaiter(Node.SHARED);
// 是否中断标识,此处也是在获取锁结束后响应中断,只有在获取中断锁(acquireSharedInterruptibly)时,才会在获取锁的过程中响应中断
boolean interrupted = false;
try {
for (;;) {
// 获取前继节点
final Node p = node.predecessor();
// 如果前继节点为头节点(成功获取了锁的节点),则此阶段有资格获取锁
if (p == head) {
//尝试获取锁
int r = tryAcquireShared(arg);
// 获取锁成功
if (r >= 0) {
// 成功获取锁后,将自身设置为等待队列的头节点(之前的头节点会出队,被GC回收),并唤醒所有后续的共享锁申请节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
// 如果没有获取锁资格,判断是否可以进入阻塞状态;如果可以,线程进入阻塞状态
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
// 如果请求共享锁发生异常,将当前节点从等待队列中删除,取消申请锁流程
cancelAcquire(node);
throw t;
} finally {
// 响应中断
if (interrupted)
selfInterrupt();
}
}
/**
* Sets head of queue, and checks if successor may be waiting
* in shared mode, if so propagating if either propagate > 0 or
* PROPAGATE status was set.
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 修改等待队列的头节点,之前的头节点出队,等待GC回收
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
// 如果资源有剩余量,并且头节点等待状态小于0(小于0表示头节点后续有阻塞节点),则继续处理后续节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果后续节点为空,重新检查等待队列,或是申请共享锁的,则唤醒后续节点
if (s == null || s.isShared())
doReleaseShared();
}
}
/**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
//记录当前头节点,后面会唤醒后继结点,如果后继结点成功获取共享锁,会修改等待队列的头节点;
Node h = head;
//
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果节点等待状态为SIGNAL,后续肯定有阻塞节点,则唤起后续节点
if (ws == Node.SIGNAL) {
// 将当前节点的等待状态置为0(初始化状态)
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后续节点
unparkSuccessor(h);
}
// 如果当前节点的等待状态为0(节点初始化,无后续节点),将节点状态修改为PRORAGATE,表示一旦出现一个新的共享结点连接在该结点后,该结点的共享锁将传播下去。
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果后继结点成功获取共享锁(获取成功后会修改头节点),结束循环
if (h == head) // loop if head changed
break;
}
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the 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)
node.compareAndSetWaitStatus(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;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
Condition/ConditionObject
在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait()、notify()系列方法可以实现等待/通知模式。在Java SE5后,Java提供了Lock接口,相对于Synchronized而言,Lock提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活。
Condition是一种广义上的条件队列。他为线程提供了一种更为灵活的等待/通知模式,Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。
ConditionObject是AQS中的内部类,提供了条件锁的同步实现,实现了Condition接口,并且实现了其中的await(),signal(),signalALL()等方法。在一个AQS同步器中,可以定义多个Condition,只需要多次lock.newCondition(),每次都会返回一个新的ConditionObject对象。在ConditionObject中,通过一个条件队列来维护条线等待的线程。所以在一个同步器中可以有多个等待队列,他们等待的条件是不一样的。
条件队列
条件队列是一个FIFO的队列,在队列的每个节点都包含了一个线程引用。该线程就是在Condition对象上等待的线程。这里的节点和AQS中的同步队列中的节点一样,使用的都是AbstractQueuedSynchronizer.Node类。每个调用了condition.await()的线程都会进入到 条件队列中去。
在Condition中包含了firstWaiter和lastWaiter,每次加入到 条件队列中的线程都会加入到 条件队列的尾部,来构成一个FIFO的 条件队列。
await()
JDK11中ConditionObject中的实现源码
public final void await() throws InterruptedException {
// 如果当前线程中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程加入到等待队列的尾部
Node node = addConditionWaiter();
// 释放线程占用的锁,如果释放出现异常,标记当前节点为CANCELLED,后续会删除此节点
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果当前线程不在同步队列中
while (!isOnSyncQueue(node)) {
// 阻塞当前线程,直到被唤醒或线程中断(调用await方法后,上面完成了加入条件队列,释放锁的过程,阻塞到此处,后续是被唤醒后的流程)
LockSupport.park(this);
// 如果线程中断,结束循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被唤醒后,会尝试去获取资源(资源数据量由之前保存)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果当前节点后续还有等待线程,清理条件队列(将当前节点清理出条件队列)
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 对中断进行处理
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}