深入理解Java中的AQS

本文深入剖析了AQS(AbstractQueuedSynchronizer)的核心机制,包括其如何利用CLH队列管理和调度线程,以及ReentrantLock非公平锁的具体实现流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

核心思想

AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去修改状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

基本原理

AbstractQueuedSynchronizer抽象队列同步器简称AQS,它是实现同步器的基础组件,juc下面Lock的实现以及一些并发工具类就是通过AQS来实现的。

  • AQS是一个通过内置的FIFO双向队列来完成线程的排队工作,元素的结点类型为Node类型
  • 其中Node中的thread用来存放进入AQS队列中的线程引用,Node结点内部的SHARED表示标记线程是因为获取共享资源失败被阻塞添加到队列中的;Node中的EXCLUSIVE表示线程因为获取独占资源失败被阻塞添加到队列中的。waitStatus表示当前线程的等待状态:
    • CANCELLED=1:表示线程因为中断或者等待超时,需要从等待队列中取消等待;
    • SIGNAL=-1:当前线程thread1占有锁,队列中的head(仅仅代表头结点,里面没有存放线程引用)的后继结点node1处于等待状态,如果已占有锁的线程thread1释放锁或被CANCEL之后就会通知这个结点node1去获取锁执行
    • CONDITION=-2:表示结点在等待队列中(这里指的是等待在某个lock的condition上,关于Condition的原理有时间再整理,当持有锁的线程调用了Condition的signal()方法之后,结点会从该condition的等待队列转移到该lock的同步队列上,去竞争lock。(注意:这里的同步队列就是我们说的AQS维护的FIFO队列,等待队列则是每个condition关联的队列)
    • PROPAGTE=-3:表示下一次共享状态获取将会传递给后继结点获取这个共享同步状态。
  • AQS中维持了一个单一的volatile修饰的状态信息state(AQS通过Unsafe的相关方法,以原子性的方式由线程去获取这个state)。AQS提供了getState()、setState()、compareAndSetState()函数修改值(实际上调用的是unsafe的compareAndSwapInt方法)。
  • AQS的设计师基于模板方法模式的。使用时候需要继承同步器并重写指定的方法,并且通常将子类推荐为定义同步组件的静态内部类,子类重写这些方法之后,AQS工作时使用的是提供的模板方法,在这些模板方法中调用子类重写的方法。其中子类可以重写的方法:
    //独占式的获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
    protected boolean tryAcquire(int arg) {	throw new UnsupportedOperationException();}
    //独占式的释放同步状态,等待获取同步状态的线程可以有机会获取同步状态
    protected boolean tryRelease(int arg) {	throw new UnsupportedOperationException();}
    //共享式的获取同步状态
    protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException();}
    //尝试将状态设置为以共享模式释放同步状态。 该方法总是由执行释放的线程调用。 
    protected int tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
    //当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占
    protected int isHeldExclusively(int arg) {	throw new UnsupportedOperationException();}

     

  • AQS的内部类ConditionObject是通过结合锁实现线程同步,ConditionObject可以直接访问AQS的变量(state、queue),ConditionObject是个条件变量 ,每个ConditionObject对应一个队列用来存放线程调用condition条件变量的await方法之后被阻塞的线程。

ReentrantLock非公平锁加锁过程

线程一获取锁

在初始情况下,线程一调用了lock方法请求获得锁,首先通过CAS的方式将state更新为1,表示自己线程一获得了锁,并将独占锁的线程持有者设置为线程一

final void lock() {
    if (compareAndSetState(0, 1))
        //setExclusiveOwnerThread是AbstractOwnableSynchronizer的方法,AQS继承了AbstractOwnableSynchronizer
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

线程二抢占锁失败

此时线程二调用lock方法,尝试通过CAS更新state,更新失败(线程一已经更新成功)。调用acquire(1)。

//(1)tryAcquire,这里线程二执行返回了false,那么就会执行addWaiter将当前线程构造为一个结点加入同步队列中
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

 非公平锁的实现中,AQS的模板方法acquire(1)就会调用NofairSync的tryAcquire方法,而tryAcquire方法又调用的Sync的  nonfairTryAcquire方法,所以我们看看nonfairTryAcquire的流程

//NofairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //(1)获取当前线程
    final Thread current = Thread.currentThread();
    //(2)获得当前同步状态state
    int c = getState();
    //(3)如果state==0,表示没有线程获取
    if (c == 0) {
        //(3-1)那么就尝试以CAS的方式更新state的值
        if (compareAndSetState(0, acquires)) {
            //(3-2)如果更新成功,就设置当前独占模式下同步状态的持有者为当前线程
            setExclusiveOwnerThread(current);
            //(3-3)获得成功之后,返回true
            return true;
        }
    }
    //(4)这里是重入锁的逻辑
    else if (current == getExclusiveOwnerThread()) {
        //(4-1)判断当前占有state的线程就是当前来再次获取state的线程之后,就计算重入后的state
        int nextc = c + acquires;
        //(4-2)这里是风险处理
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //(4-3)通过setState无条件的设置state的值,(因为这里也只有一个线程操作state的值,即
        //已经获取到的线程,所以没有进行CAS操作)
        setState(nextc);
        return true;
    }
    //(5)没有获得state,也不是重入,就返回false
    return false;
}

插入队列

尝试获取失败,调用addWaiter方法执行创建节点插入队列操作

private Node addWaiter(Node mode) {
    //(1)将当前线程以及阻塞原因(是因为SHARED模式获取state失败还是EXCLUSIVE获取失败)构造为Node结点
    Node node = new Node(Thread.currentThread(), mode);
    //(2)这一步是快速将当前线程插入队列尾部
    Node pred = tail;
    if (pred != null) {
        //(2-1)将构造后的node结点的前驱结点设置为tail
        node.prev = pred;
        //(2-2)以CAS的方式设置当前的node结点为tail结点
        if (compareAndSetTail(pred, node)) {
            //(2-3)CAS设置成功,就将原来的tail的next结点设置为当前的node结点。这样这个双向队列就更新完成了
            pred.next = node;
            return node;
        }
    }
    //(3)执行到这里,说明要么当前队列为null,要么存在多个线程竞争失败都去将自己设置为tail结点,
    //那么就会有线程在上面(2-2)的CAS设置中失败,就会到这里调用enq方法
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        //(4)还是先获取当前队列的tail结点
        Node t = tail;
        //(5)如果tail为null,表示当前同步队列为null,就必须初始化这个同步队列的head和tail(建
        //立一个哨兵结点)
        if (t == null) { 
            //(5-1)初始情况下,多个线程竞争失败,在检查的时候都发现没有哨兵结点,所以需要CAS的
            //设置哨兵结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } 
        //(6)tail不为null
        else {
            //(6-1)直接将当前结点的前驱结点设置为tail结点
            node.prev = t;
            //(6-2)前驱结点设置完毕之后,还需要以CAS的方式将自己设置为tail结点,如果设置失败,
            //就会重新进入循环判断一遍
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

第一次循环tail指针为空,新建Node节点。此时AQS中数据

第二次循环创建线程二对应的Node节点挂到head节点后

再次尝试获取锁,失败挂起线程

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //在这样一个循环中尝试tryAcquire同步状态
        for (;;) {
            //获取前驱结点
            final Node p = node.predecessor();
            //(1)如果前驱结点是头节点,就尝试取获取同步状态,这里的tryAcquire方法相当于还是调
            //用NofairSync的tryAcquire方法,在上面已经说过
            if (p == head && tryAcquire(arg)) {
                //如果前驱结点是头节点并且tryAcquire返回true,那么就重新设置头节点为node
                setHead(node);
                p.next = null; //将原来的头节点的next设置为null,交由GC去回收它
                failed = false;
                return interrupted;
            }
            //(2)如果不是头节点,或者虽然前驱结点是头节点但是尝试获取同步状态失败就会将node结点
            //的waitStatus设置为-1(SIGNAL),并且park自己,等待前驱结点的唤醒。至于唤醒的细节
            //下面会说到
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(1)获取前驱结点的waitStatus
    int ws = pred.waitStatus;
    //(2)如果前驱结点的waitStatus为SINGNAL,就直接返回true
    if (ws == Node.SIGNAL)
        //前驱结点的状态为SIGNAL,那么该结点就能够安全的调用park方法阻塞自己了。
        return true;
    if (ws > 0) {
        //(3)这里就是将所有的前驱结点状态为CANCELLED的都移除
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //CAS操作将这个前驱节点设置成SIGHNAL。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

acquireQueued()方法先判断当前传入Node前置节点是否为head,是则尝试加锁。加锁成功将当前节点设置为head节点,然后空置之前的head节点,方便垃圾回收掉。

如果加锁失败或者Node的前置节点不是head节点,通过shouldParkAfterFailedAcquire方法 将head节点的waitStatus变为了SIGNAL=-1,最后执行parkAndChecknIterrupt方法,调用LockSupport.park()挂起当前线程

此时线程二就静静的待在AQS的等待队列里面了,等着其他线程释放锁来唤醒它

线程三抢占锁失败,插入队列

非公平锁的释放流程

 unlock方法,实际调用了AQS的release()方法

public void unlock() {
    sync.release(1); //这里ReentrantLock的unlock方法调用了AQS的release方法
}
public final boolean release(int arg) {
	//这里调用了子类的tryRelease方法,即ReentrantLock的内部类Sync的tryRelease方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放锁

protected final boolean tryRelease(int releases) {
    //(1)获取当前的state,然后减1,得到要更新的state
    int c = getState() - releases;
    //(2)判断当前调用的线程是不是持有锁的线程,如果不是抛出IllegalMonitorStateException
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //(3)判断更新后的state是不是0
    if (c == 0) {
        free = true;
        //(3-1)将当前锁持者设为null
        setExclusiveOwnerThread(null);
    }
    //(4)设置当前state=c=getState()-releases
    setState(c);
    //(5)只有state==0,才会返回true
    return free;
}

唤醒结点

private void unparkSuccessor(Node node) {
    //(1)获得node的waitStatus
    int ws = node.waitStatus;
    //(2)判断waitStatus是否小于0
    if (ws < 0)
        //(2-1)如果waitStatus小于0需要将其以CAS的方式设置为0
        compareAndSetWaitStatus(node, ws, 0);

    //(2)获得s的后继结点,这里即head的后继结点
    Node s = node.next;
    //(3)判断后继结点是否已经被移除,或者其waitStatus==CANCELLED
    if (s == null || s.waitStatus > 0) {
        //(3-1)如果s!=null,但是其waitStatus=CANCELLED需要将其设置为null
        s = null;
        //(3-2)会从尾部结点开始寻找,找到离head最近的不为null并且node.waitStatus<=0的结点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //(4)node.next!=null或者找到的一个离head最近的结点不为null
    if (s != null)
        //(4-1)唤醒这个结点中的线程
        LockSupport.unpark(s.thread);
}

总体流程

参考文章

https://www.cnblogs.com/fsmly/p/11274572.html

https://blog.youkuaiyun.com/qq_18221459/article/details/106317155

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值