AQS总结

109 阅读6分钟

AQS总结

AQS介绍

AQS是用来实现锁或者其他同步器组件的公共基础部分的抽象实现是重量级的基础框架及整个JUC体系的基石,主要用于解决锁分配给谁的问题;整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int变量表示持有锁的状态。ReentrantLock、CountDownLatch、读写锁、Semphone底层都是AQS。

从ReentrantLock分析AQS(以非公平锁为例)

初始化ReentrantLock对象。

ReentrantLock reentrantLock = new ReentrantLock();//非公平锁

无参构造,追进去->

    private final Sync sync;
    
    public ReentrantLock() {
        sync = new NonfairSync();//默认是非公平锁
    }

Sync是ReentrantLock的静态内部类,继承自AQS,即AbstractQueuedSynchronizer

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
​
        abstract void lock();
​
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
​
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
​
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
​
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
​
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
​
        final boolean isLocked() {
            return getState() != 0;
        }
​
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
​

ReentrantLock内部还由两个静态内部类:NonfairSync和FairSync,表示当前的ReentrantLock是公平锁还是非公平锁。NonfairSync和DFairSync继承了Sync,并且重写了lock方法和tryAcquire方法。

1692871762698.png

1692871787192.png

获取锁

reentrantLock.lock();

默认是非公平锁,走的是NonfairSync的lock方法,跟进去。

        final void lock() {
            acquire(1);//参数1表示计数器,当前线程获取锁加1
        }

acquire是AbstractQueuedSynchronizer内部的方法,尝试获取锁。

acquire方法:

    //传进来的arg是1
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

该方法内部首先通过tryAcquire方法尝试获取锁。如果获取锁失败并且当前线程加入了队列,当前线程自旋等待获取锁。

tryAcquire方法:

是AQS内部的方法,但是没有具体实现,具体实现是由ReentrantLock的NonfairSync和FairSync实现。

    //AQS内部的tryAcquire
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    
    //NonfairSync
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

nonfairTryAcquire非公平锁获取锁,传的参数acquires是1,该方法在ReentrantLock的内部类Sync内部,跟进去->

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

1、获取当前线程

2、获取state,state是AQS的属性,表示当前锁的状态,state=0表示当前没有线程获取锁,通过CAS获取锁

3、如果CAS获取锁成功,setExclusiveOwnerThread(current); 将当前线程设置为锁的独占拥有者,直接返回

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    //exclusiveOwnerThread是AbstractOwnableSynchronizer类中的属性
    //private transient Thread exclusiveOwnerThread;
    //AQS继承自AbstractOwnableSynchronizer

4、如果已经有线程获取锁了(state!=0),判断当前线程是不是占有锁的线程,如果是同一个线程,将state+1,如果溢出了(超过整型),抛出异常。更新state的状态。返回获取锁成功

5、返回false,表示获取锁失败。

回到非公平锁的acquire方法:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

执行addWaiter(Node.EXCLUSIVE),AQS内部的方法,传递参数加独占锁而非共享锁。返回当前Node

Node是AQS内部的静态内部类

1692876537188.png

    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;
    }

1、将当前线程以及锁的形式封装成Node对象

2、如果尾节点不为null,表示之前已经有线程在等待了,CAS方式将当前线程变为尾节点。成功放入等待队列就返回当前封装好的节点。

3、尾节点为空。进入enq方法,该方法内部一直自旋,如果尾节点为空,需要先进行初始化,tail = head。直到尾节点不为空,将当前线程放入等待队列。

    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方法,传入参数,addWaiter返回的node,以及参数arg = 1。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                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);
        }
    }

该方法实际上是当前线程一直在自旋等待获取锁。

1、failed初始化为true,标记是否获取资源失败。

2、自旋。获取当前节点的前驱节点。 如果 p 是头节点,且可以通过 tryAcquire(arg) 方法获取资源成功,则将当前节点 node 设置为头节点,标记获取成功,解除当前节点与前驱节点的连接(帮助 GC),然后设置 failedfalse,并返回可能被中断的状态。

3、获取锁失败,判断是否需要被阻塞。

shouldParkAfterFailedAcquire(p, node) 方法用于判断当前线程是否应该被阻塞等待,当它尝试获取资源时失败了。这个方法的目的是确保在适当的情况下,将线程阻塞起来,以避免不必要的忙等待,从而减少对 CPU 资源的浪费。
​
具体解释如下:
​
假设有一个线程等待获取资源,它表示为当前节点 node。
​
node 的前驱节点(previous node)是 p。
​
方法会检查以下情况:
​
p 是否是头节点(最前面的节点)。
头节点的状态是否是 SIGNAL。
如果满足上述两个条件,意味着前驱节点(通常是头节点)已经知道自己要释放资源,并且有可能会通知后继节点(当前节点 node 就是一个后继节点)。所以,node 不需要进行忙等待,而是可以安心地被阻塞等待,直到被前驱节点通知。
​
如果不满足上述条件,意味着前驱节点不知道是否要释放资源,或者不会通知后继节点。在这种情况下,当前节点 node 不应该被阻塞,它应该继续尝试获取资源。
​
总之,shouldParkAfterFailedAcquire(p, node) 方法的目标是避免忙等待,以提高效率。如果当前节点的前驱节点已经知道会释放资源,并且会通知后继节点,那么当前节点就可以安全地被阻塞,直到前驱节点通知。如果前驱节点不知道是否会释放资源,那么当前节点应该继续尝试获取资源,而不是浪费时间在无用的忙等待上。

两种情况,会导致parkAndCheckInterrupt()的阻塞结束: 1.当前持有锁的线程,释放锁以后,将这个线程unpark()了,此时该线程一定排在队列的头部(不包括head节点) 2.线程被interrupt了

释放锁

    public void unlock() {
        sync.release(1);
    }

release方法释放锁并且唤醒等待队列的后继节点。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  1. tryRelease(arg): 首先尝试以给定的参数 arg 释放资源。这是一个抽象方法,需要由继承 AbstractQueuedSynchronizer 的类实现。如果资源成功释放,此方法返回 true,否则返回 false

  2. 如果资源成功释放(tryRelease 返回 true),则进行下一步操作。

    • Node h = head;: 获取等待队列的头节点。
    • 如果头节点 h 不为 null,并且头节点的 waitStatus 不为 0(即头节点标记了某种状态,例如可能是 SIGNAL),则调用 unparkSuccessor(h) 方法唤醒等待队列中的后继节点。
    • 返回 true,表示资源释放成功。
  3. 如果资源释放不成功(tryRelease 返回 false),则直接返回 false

总之,release 方法用于释放资源并通知等待队列中的后继节点。在并发环境中,当一个线程完成了某个操作并释放了锁或其他资源时,调用 release 方法可以确保等待队列中的其他线程有机会获取资源。这种机制是实现线程协作和同步的关键部分。

tryRelease方法:

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

公平锁

公平锁有细微差别,在tryAcquire方法中:

只要当前等待队列没有前驱节点时才能够尝试获取锁。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            //这里差别
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }