1.什么是 AQS ?
AQS 全称是AbstractQueuedSynchronizer ,是 java.util.concurrent.locks 下一个非常重要的类,俗称 同步器 。很多同步锁都是基于这个同步器设计的,如ReentrantLock、CounterDown等
2.AQS的原理
AQS的核心思想是:
如果请求的共享资源空闲,则将当前请求的资源设置为有效的工作线程,并且将共享资源设置为锁定状态;
如果请求的共享资源被占用,那么就将暂时获取不到锁的线程放到等待队列中。这个队列是一个CLH双向队列(虚拟的双向队列,即不存在队列实例,仅存在节点之间的关联关系),AQS将每条请求共享资源的线程封装成一个CLH队列的节点来实现锁的分配。
3.AQS的常见方法和成员变量
3.1 state
维持了一个单一的共享状态变量 state 来实现同步器同步。
state == 0 表示当前没有线程获得锁
stae == 1 表示已经有线程持有锁
state >1 表示有线程重入了锁 (可重入锁,重入一次 +1 ,unlock一次 -1)
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
state 设计的亮点
state 使用 volatile修饰,保证多线程中的可见性 getState setState 方法采用final修饰,限制AQS的子类重写它们俩 compareAndSetState 采用乐观思想的CAS算法,也采用final修饰,不允许子类重写
3.2 Node节点
CLH同步队列中,一个线程被封装成一个Node节点 。这个节点中保存了线程的引用(thread) 、状态 (waitStatus) 、前驱节点(prev)、后继节点(next)、condition队列的后续节点 (nextWaiter)
其中需要特别说明的是 waitStatus几种状态: waitStatus
/** waitStatus value to indicate thread has cancelled */
// 表示当前节点的线程因为超时或者中断被取消了
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
//表示当前节点的后续节点中的线程通过park被阻塞了,当前节点在释放或取消时要通过 unpark接触它们的阻塞
//这个状态比较难理解 signal 通知,也就是说这个节点要通知下一个节点,在锁中signal 也即表示唤醒 参考wait signal signalAll
static final int SIGNAL = -1;
//表示当前队列在condition 队列中
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
//共享模式的头节点可能处于此状态 ,表示无条件往下传播,引入此状态是为了优化锁竞争,使队列中线程有序地一个个唤醒
static final int PROPAGATE = -3;
默认的0 初始节点状态的值就是 0
//生成node节点的逻辑
Node node = new Node(Thread.currentThread(), mode);
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
3.2.1 CLH入列
在AQS中除了state之外,还有2个重要属性 head tail
/**
* 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;
CLH入列的时候,就需要 tail 指向 新节点 ,新节点的prev 指向当前最后的节点,当前最后一个节点的next指向新节点
//获取锁失败的情况下,用当前线程生成node节点
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;
}
}
//队列不存在,则通过cas + 自旋的方式将新node节点设置为 head tail
enq(node);
return node;
}
3.2.2 CLH出列
首节点的线程释放同步状态后,将会唤醒它的后继节点 (next) ,而后继节点将会在获取同步状态成功后将自己设置为首节点。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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)
// > 0 那就是cancel状态了 ,不用管
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;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒下一个节点
if (s != null)
LockSupport.unpark(s.thread);
}
3.3 Condition
CondtionObject队列与CLH队列的关系:
- 调用了 await 方法的线程,会被加入到 ConditionObject等待的队列 ,并且唤醒 CLH队列中head节点的下一个节点
- 线程在某个ConditionObject对象上调用了singnal方法后,等待队列中的firstWatier会被加入到 AQS的CLH队列中,等待被唤醒
- 当线程调用unLock 方法释放锁时,CLH队列中的head节点的下一个节点会被唤醒
//独占式 获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法需要由实现类自己实现。
//循环等待获取锁,直到获取成功为止 在等待过程中如果发生了中断,则暂时不处理但会记录下来,等获取到锁之后进行自我中断
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)) {
//锁获取成功,当前节点就变成 head 了
setHead(node);
//原本锁在前一节点 p 上, 现在当前节点获得了锁 变成了新的head ,因此要断开前一节点p 指向当前节点
p.next = null; // help GC
failed = false;
return interrupted;
}
//根据前一个节点的状态,看当前节点是否需要park
//加入前一个节点的状态是 singal ,那么当前节点park即可 ,因为前一个节点释放的时候会unpark当前节点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4. AQS的模板方法设计模式
模板方法模式: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法可以使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤。
该类中的常见模板方法如下:
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false
5.自定义一个同步器
package com.threadtest.threadpratice.test.aqs;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Slf4j
public class TestAqs {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(()-> {
lock.lock();
//测试锁是否可重入
// log.debug("locking1...");
// lock.lock();
// log.debug("locking2...");
try {
log.debug("locking...");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t1").start();
new Thread(()-> {
lock.lock();
try {
log.debug("locking...");
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t2").start();
}
}
//自定义锁-不可重入锁
class MyLock implements Lock {
//同步器类
//独占锁
class MySync extends AbstractQueuedLongSynchronizer{
@Override
protected boolean tryAcquire(long arg) {
if (compareAndSetState(0,1)) {
//加上了锁
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(long arg) {
//解锁
setExclusiveOwnerThread(null);
//state是 volatile修饰,放下面 防止指令重排序
setState(0);
return true;
}
//是否持有
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private MySync sync = new MySync();
//加锁 尝试不会成功,进入等待队列
@Override
public void lock() {
sync.acquire(1);
}
//加锁(可打断)
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//尝试加锁 ,加一次;加锁不成功,返回false
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
//创建条件变量
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
可以看到引入AQS之后,自定义同步锁很简单