AQS是什么?
AQS 全称是 AbstractQueuedSynchronizer, 它提供一种依赖于FIFO等待队列的构建锁和同步器的框架。
CAS是什么?
CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
底层实现
整体结构
-
AQS 的结构大概可总结为以下 3 部分:
- 用 volatile 修饰的整数类型的 state 状态,用于表示同步状态,提供 getState 和 setState, compareAndSetState来操作同步状态;
- 提供了一个 FIFO 等待队列,实现线程间的竞争和等待,这是 AQS 的核心;其中, 链表头Head和链表尾Tail也有volatile修饰。
- AQS 内部提供了各种基于 CAS 原子操作方法,如 compareAndSetState 方法,并且提供了锁操作的acquire和release方法。
-
提供两种锁的默认实现方式:
- 独占锁(Exclusive)
- 共享锁(Shared)
tryAcquire, tryRelease 都是需要实现类自己去实现的方法, 如果不实现的话,是会抛出异常UnsupportedOperationException的
用到的设计模式-模板模式, 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
独占锁
acquire获取独占锁
- 伪代码实现
while (!tryAcquire(arg)) {
<em>enqueue thread if it is not already queued</em>;
<em>possibly block current thread</em>;
}
- 代码实现
- 先尝试获取锁,获取成功则成功
- 尝试失败,则把当前线程包装成为一个节点,然后等待获取的机会
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- NOTE这里有必要说明一下,就是当一个节点成为head节点的时候,他不一定会是下一个获取锁的节点,从上面代码也可以看出来,所以获取锁的线程都会先尝试获取锁一次,这样有可能等待队列的头节点也可能获取锁失败。
release释放独占锁
- 伪代码实现
if (tryRelease(arg))
<em>unblock the first queued thread</em>;
- 代码实现
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
共享锁
acquireShared获取共享锁
- 代码实现
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
releaseShared释放共享锁
- 代码实现
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
FIFO队列
队列模型
这个在AQS的注释说明里边,Doug Lea已经说的很明确了,还做了图解
- 这个等待队列是CLH(Craig, Landin, and Hagersten)队列的一个变种,这种队列常被用作自旋锁(这个概念就不展开了)
- 作用:用于阻塞同步器
- 结构
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
- 入队:插入队尾
- 出队:直接设置head即可
节点
- 节点其实是把想要获取锁的线程包装了一番
//mode分exclusive和shared两种模式
new Node(Thread.currentThread(), mode);
- 节点状态
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate.
* 这个状态只在共享锁的模式下有效,这个传播的用处在哪儿呢?
* 举例说明:读写锁,写读操作和写写操作互斥,读读之间不互斥;当调用acquireShared获取读
* 锁时,会检查后续节点是否是获取读锁,如果是,则同样释放;
*/
static final int PROPAGATE = -3;
常见面试
- 谈一下AQS吧 @可以从定义入手,然后讲
- 不同锁状态的更改的实现方式
- FIFO队列的实现方式
- 核心技术CAS+volatile
- CAS是什么?见上面的笔记
- 为什么你说AQS的底层是CAS+volatile?
- 表示锁状态的变量state,以及FIFO队列的头,尾,节点的状态都是volatile修饰的
- 在设置state,队列的头,尾,状态的时候都有用到CAS技术
- JUC包里的同步组件主要实现了AQS的哪些主要方法 ?
- tryAcquire, tryRelease
- tryAcquireShared, tryReleaseShared
- isHeldExclusively