AQS介绍
- AbstractQueuedSynchronizer是用于构建同步锁的框架,其中维护了一个
**volatile int state**
,和一个**FIFO**
队列**CLH**
,AQS为**state**
提供了**CAS**
操作的方法。 **volatile**
保证了变量**state**
在多线程环境下的**可见性**
,在**state=0**
时为没有线程占有锁,当某个线程使用CAS操作将state改为1后,则说明该线程抢占到了锁。此时其他没有抢占到锁的线程则需要进入到CLH队列中**等待**
,并且这些线程会被**LockSupport.park()**
操作挂起,当占有锁的线程释放锁资源后,会唤醒队列中的线程进行争抢。- 实现类只需要通过继承AQS后,实现其中的抽象方法即可,并且可以使用或重写AQS提供的关于state和CLH队列的相关的模板方法。
AQS模板
- AQS提供了以下
**模板方法**
,实现类在继承AQS后,只需要重写相关的方法,即可快速开发同步工具类 - 如果只需要创建
**排它锁**
则只需要重写**tryAcquire**
和**tryRelease**
方法 - 如果只需要创建
**共享锁**
则只需要重写**tryAcquireShared**
和**tryReleaseShared**
方法 - 在
**没有重写**
的状态下会抛出**UnsupportedOperationException**
异常
tryAcquire(int); // 尝试获取独占锁,可获取返回true,否则false
tryRelease(int); // 尝试释放独占锁,可释放返回true,否则false
tryAcquireShared(int); // 尝试以共享方式获取锁,失败返回负数,只能获取一次返回0,否则返回个数
tryReleaseShared(int); // 尝试释放共享锁,可获取返回true,否则false
isHeldExclusively(); // 判断线程是否独占资源
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
CLH队列以及Node
队列首尾节点
/**
* 等待队列的头部,延迟初始化。除了初始化之外,只能通过setHead方法进行修改。
* 注意:如果 head 存在,则保证其 waitStatus 不会被 CANCELLED
*/
private transient volatile Node head;
/**
* 等待队列的尾部,延迟初始化。仅通过方法 enq 修改以添加新的等待节点。
*/
private transient volatile Node tail;
Node
**waitStatus**
节点的状态**CANCELLED**
1 节点超时或已被取消**SIGNAL**
-1 后继节点需要被唤醒时,当前节点为SIGNAL。当队列中添加后继节点后被挂起后,前一个节点的状态会改为SIGNAL**CONDITION**
-2 进入**Condition**
队列的状态**PROPAGATE**
-3 节点在**releaseShared**
释放共享锁时使用- 普通节点 0
**prev**
指向前一个节点的指针**next**
指向下一个节点的指针**thread**
当前节点所有的线程**nextWaiter**
condition条件队列
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 值指示下一个 acquireShared 应无条件传播
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
*/
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
独占锁acquire
acquire
- 在
**尝试获取锁前**
,会先调用**tryAcquire**
方法进行尝试获取锁 - 如果尝试获取锁失败后,则会添加到
**CLH队列**
中 - 如果在CLH队列获取锁的过程中被park挂起,就会存在中断标记,在方法内因为避免死循环会将中断标记去除,在这里会将中断标记进行补充。
@ReservedStackAccess
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter和enq
**addWaiter**
的方法作用是在当前线程争抢锁失败后,将该**线程包装成一个Node节点**
- 并且将该Node节点通过
**CAS**
的方式添加到CLH**队列的尾部**
- 在多线程环境下添加队列可能会出现竞争情况,如果一次CAS添加没有成功,就会调用进入到enq方法
- enq方法通过
**自旋+CAS**
的方式,确保该线程所在的Node节点成功添加到CLH队列尾部
private Node addWaiter(Node mode) {
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;
}
- enq方法中会自旋操作,在第一次进入enq方法时,head和tail都指向null
- 此时会先调用 compareAndSetHead(new Node()) ,此时head节点会指向哨兵节点,同时将 tail指向哨兵节点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued
- 进入
**acquireQueued**
方法后会**自旋**
获取锁,当且**仅当该节点为第一个节点**
时才能尝试获取锁 - 如果该
**节点不是第一个节点或没有争抢到锁**
,需要调用**shouldParkAfterFailedAcquire**
方法,找到**真正的首节点**
后,将其改为**SIGNAL**
状态,然后该线程会被**LockSupport.park(this)**
挂起 - 线程由挂起状态被唤醒,因为是使用的
**Park**
挂起,此时该线程仍存在中断标记,但此时需要使用
**Thread.interrupted()**
来清除中断标记,否则在自旋获取锁的过程中,如果该线程在 tryAcquire()中**争抢锁失败**
,此时再次被 **LockSupport.park(this)**
挂起时,会因为该线程**已经存在中断标记而无法正常挂起**
- 这样会导致进入
**死循环空转**
的情况,可能会出现CPU100%的情况,因此需要用**interrupted**
变量记录下线程被打断,在**acquire**
方法中根据此处的返回值,将**中断标记重新赋值**
。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取前置节点
final Node p = node.predecessor();
//当且仅当前置节点为head节点时
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);
}
}
//该方法的返回值为true 说明该线程应当被park挂起
//否则不应当被挂起 应当继续自旋获取锁
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前置节点的状态已经挂起 则不需要进行处理
return true;
if (ws > 0) {
//线程的状态仅有cancel是大于0的
//会一直向前寻找到正常状态的Node节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
独占锁release
release
- 在真正释放锁前,会先调用
**tryRelease()**
,这是子类的实现方法 - 如果tryRelease成功,则会判断该节点状态是否需要唤醒后续节点
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor
- 如果哨兵节点的状态小于0,则将其状态设置为正常状态 0
- 会唤醒哨兵的下一个节点,如果后继节点被取消,或是后继节点已过期,就会从队列的尾部向前遍历,找到最靠近哨兵的挂起节点,然后将该节点唤醒
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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);
}
共享锁
acquireShared
- 先调用 tryAcquireShared ,负数代表获取锁失败
- 与独占锁中 acquireQueued 流程类似,先创建包装当前线程的Node节点,然后尝试自旋获取锁
- 在获取锁失败后会调用 addWaiter(Node.SHARED)加入到 CLH队列的尾部,此时会被 park挂起等待获取锁,直至该节点移动到第一个节点后才会被唤醒获取锁
-
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryReleaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
条件变量
AQS主要实现类
Semaphore(信号量)
- 连接池,用于限制同时访问的上限
- 当达到设定的上线后,再次调用acquire方法会被阻塞
CountDownLatch(不可重用)
- 用于倒计时所有的线程都完成任务,await代表归零,countdown代表-1,代表完成了一个任务
- 构造方法用于初始化数值
CyclicBarrier(可重用)
- 可以理解为人满发车
- 构造函数初始化需要的数量
- await表明完成了一个,当数量不够时会进入阻塞
- 当其他的线程也完成任务后,则说明任务完成,结束阻塞