Lock、AbstractQueuedSynchronizer

本文详细介绍了Java并发中的Lock接口及其内部实现AbstractQueuedSynchronizer(AQS)。AQS是一个模板类,用于构建锁和其他同步组件,包括独占锁的获取和释放、可中断和超时获取锁的机制。文章通过分析AQS的数据结构和核心方法,揭示了其同步队列的工作原理。

一、Lock简介

Lock是并发访问共享资源的另一种方式,当需要使用chain lock这种时会很灵活,jdk文档中举了一个例子:(For example, some algorithms for traversing concurrently accessed data structures require the use of "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on.)

而Lock通常可以结合Condition来使用(通过lock.newCondition()方法获得),当使用Lock来替代Synchronized同步方法或者同步语句块时,Condition替代对象监视器方法(wait(),notify(),notifyAll())

二、AQS简介

AQS是一个抽象类,使用了模板设计模式,定义了一些抽象方法供子类重写,在AQS一些方法中会调用这些被子类重写的方法。

在文章初识Lock与AbstractQueuedSynchronizer(AQS)中对AQS进行了总结:

三、AQS详解 
1、排队区分为同步队列和条件队列。同步队列只有一个,条件队列可以有多个,只有获取锁后才能进入条件队列

同步队列的结构是怎样的呢?

查看AQS源码,看到两个字段:

    /**
     * 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;

得知AQS的同步队列是由头结点和尾结点维护的,那么再看看Node里面的内容:

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;
        /** 线程取消 */
        static final int CANCELLED =  1;
        /** 后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行 */
        static final int SIGNAL    = -1;
        /** 在同步队列等待 */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
        /** 节点状态(SIGNAL、CANCELLED、CONDITION、PROPAGATE、0)*/
        volatile int waitStatus;
        /** 前驱节点 */
        volatile Node prev;
        /** 后继结点 */
        volatile Node next;
        /** 同步队列的下一个结点,或者是特殊值SHARED。因为同步队列只有在独占模式下使用,通过这个字段可以表明是否是共享模式 */
        Node nextWaiter;
        ...
    }

可以看到Node拥有前驱和后继,所以同步队列是一个双向队列,在AQS中管理同步队列的数据结构大致如下(摘自深入理解AbstractQueuedSynchronizer(AQS)):

2、独占锁

(1)独占锁获取锁

使用Lock的lock实际上会调用acquire方法。

    public final void acquire(int arg) {
        /** 尝试以独占的方式获取锁,获取成功则返回,否则将线程包装成结点(独占模式)并放入同步队列 */
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

① tryAcquire方法

    //Attempts to acquire in exclusive mode.(需要被子类覆盖)
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

② addWaiter方法

    /**
     * 创建入队结点
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //采用尾插法
        //如果尾结点不为null,直接插入队尾
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果尾结点为null,此处会进行初始化入队的操作
        enq(node);
        return node;
    }
    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方法

     /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//1.获得前继结点
                if (p == head && tryAcquire(arg)) {//2.如果前继结点是头结点,并且成功获取同步状态,即可以获得独占式锁
                    setHead(node);//3.成功后设置头结点
                    p.next = null; // 释放前继结点
                    failed = false;
                    return interrupted;
                }
                //4.获取锁失败尝试将该结点挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//shouldParkAfterFailedAcquire返回true的话,通过parkAndCheckInterrupt阻塞线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);//如果失败,取消尝试获取
        }
    }
    /**
     * 获取锁失败的时候检查和更新状态.
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             *前继结点状态已经是signal了,当前继结点释放锁时会通知当前结点,所以可以安全地挂起了
             */
            return true;
        if (ws > 0) {
            /*
             * 前继结点可能由于超时或中断被取消了,需要重新找到状态不是被取消的结点
             * 该步骤如图1所示
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 此时waitStatus只会是0或者PROPAGATE.表示需要signal状态但是暂时不挂起.
             * 使用CAS设置前继结点状态为Signal,外层死循环会重新调用                        
             * shouldParkAfterFailedAcquire(),直到compareAndSetWaitStatus设置成功,
             * 此时前继结点状态为signal,返回true,可以安心挂起了呀
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    /**
     * Cancels an ongoing attempt to acquire.
     *
     * @param node the node
     */ 
     private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;//1.将结点里包装的线程置空,node不再关联到任何线程

        // 2.向前找到一个有效的、没有被取消的前继结点
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;//实际上就是node

        node.waitStatus = Node.CANCELLED;//3.将node的waitStatus置为CANCELLED

        //4.如果node是tail,更新tail为pred,并使pred.next指向null
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            //5. 如果node既不是tail,又不是head的后继节点(pred != head)
            //则将node的前继节点的waitStatus置为SIGNAL
            //并使node的前继节点指向node的后继节点(相当于将node从队列中删掉了)
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //6. 如果node是head的后继节点,则直接唤醒node的后继节点
                unparkSuccessor(node);
            }
            node.next = node; // help GC 后继指向自己
        }
    }

如果是步骤6的情况 :node是head的后继节点,后继指向自己后,队列如下图所示,此时node被取消了,但还在队列中

下面对cancelAcquire的解析内容摘抄修改自(Java AbstractQueuedSynchronizer源码阅读3-cancelAcquire()作者:lzwang2)

cancelAcquire()调用了unparkSuccessor()
不过,unparkSuccessor()中并没有对队列做任何调整呀。
这次,cancelAcquire()对于出队这件事情可以说是啥都没干。
出队操作实际上是由unparkSuccessor()唤醒的线程执行的。
unparkSuccessor()会唤醒successor关联的线程(暂称为sthread),当sthread被调度并恢复执行后,将会实际执行出队操作。
现在需要搞清楚sthread是从什么地方恢复执行的呢?这要看sthread是在哪里被挂起的。在哪里跌倒的,就在哪里站起来。
本文开头在使用场景中,列出了调用cancelAcquire()的所有接口,也正是在这些接口中,线程将有可能被挂起。这些方法的代码结构类似,主体是一个for循环。这里以acquireQueued()为例,如下所示:
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;
}

sthread当初就是被parkAndCheckInterrupt()给挂起的,恢复执行时,也从此处开始重新执行。sthread将会重新执行for循环,执行到shouldParkAfterFailedAcquire()处。shouldParkAfterFailedAcquire()中将会调整successor的prev指针(同时也调整head的next指针),从而完成了node的出队操作。

此时变为下图:

 (2)独占锁释放锁

    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//如果同步状态释放成功(tryRelease返回true)
            Node h = head;
            if (h != null && h.waitStatus != 0)//队列头结点不为空且状态不为0
                unparkSuccessor(h);//唤醒 h 的后继结点
            return true;
        }
        return false;
    }
    /** 尝试设置释放的状态 */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * 1.如果node的状态小于0,,(i.e., possibly needing signal),尝试使用CAS设置状态为0
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 2.唤醒node的后继结点。如果后继结点被取消或者是空,从队尾开始遍历
         * 找到没有被取消的后继结点
         */
        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);//唤醒结点线程
    }

每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,验证了获得锁的过程是一个FIFO(先进先出)的过程。

(3)可中断式获取锁

调用lock.lockInterruptibly()实际上调用的是AQS的acquireInterruptibly

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))//获取失败
            doAcquireInterruptibly(arg);
    }
    /**
     * Acquires in exclusive interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //线程被中断时抛出异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

可以看到与 acquireQueued逻辑基本一致,只是parkAndCheckInterrupt返回true时,在acquireQueued通过一个布尔类型的标志interrupted = true 来表示中断,而doAcquireInterruptibly中断时直接抛出异常。

(4)超时等待获取锁

调用lock.tryLock(timeout,TimeUnit)实际上调用了AQS的tryAcquireNanos

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;//1. 根据超时时间和当前时间计算出截止时间
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//2.成功获取,出队
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();//3.1 重新计算超时时间
                if (nanosTimeout <= 0L)//3.2 已经超时,直接返回false
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 3、共享锁

(1)共享锁获取(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    /**
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    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) {//成功获取 等于0表示不用唤醒后继节点,大于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);
        }
    }
    /**
     * 设置队列头, 以及检查是否有共享模式的后继结点在等待, if so propagating if either     
     * propagate > 0 or PROPAGATE status was set.
     *
     * @param node the node(成功获取锁的结点)
     * @param propagate the return value from a tryAcquireShared
     */
     private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below

        //注:这里是获取到锁之后的操作,不需要并发控制
        setHead(node);//设置新的头结点,即把当前获取到锁的节点设置为头节点
        /*
         * 1.propagate > 0 表示调用方指明了后继节点需要被唤醒
         * 2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果当前节点的后继节点是共享类型或者没有后继节点,则进行唤醒
            //这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
            if (s == null || s.isShared())//只唤醒同样是共享模式的后继结点
                doReleaseShared();//详细查看下面释放锁的解析
        }
    }

 (2)共享锁释放(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少

    //释放共享模式的锁
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    /**
     * Release action for shared mode -- signals successor and ensures
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
    private void doReleaseShared() {
        for (;;) {
             //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
            //其实就是唤醒上面新获取到共享锁的节点的后继节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                     //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
            //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
             //如果头结点没有发生变化,表示设置完成,退出循环
            //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
            if (h == head)                   // loop if head changed
                break;
        }
    }

注:上面的setHeadAndPropagate()方法表示等待队列中的线程成功获取到共享锁,这时候它需要唤醒它后面的共享节点(如果有),但是当通过releaseShared()方法去释放一个共享锁的时候,接下来等待独占锁跟共享锁的线程都可以被唤醒进行尝试获取。

处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功,所以需要判断头结点是否发生了变化。 

4、总结

头节点可以认为就是当前占有锁资源的节点
A、独占模式
(1)不可中断:
I.获取锁
   ①线程先去尝试获取锁,如果获取失败后该线程被包装成节点Node,进入同步队列,如果同步队列尾结点不为空,将该节点设置为新的尾结点,否则进入同步队列的初始化。
   ②此时排队的线程仍未被挂起,如果这个节点是同步队列第一个节点,它会再次去尝试获取锁,获取失败的话会尝试将该节点挂起。判断如果该线程前继结点的状态为signal,则直接挂起,否则可能被挂起后前继结点不会唤醒它,所以用CAS设置前继结点状态为Signal(这期间线程不响应中断,只是设置一个中断的标志interrupted = true)
II.释放锁
    释放锁的顺序是从队头H开始的,如果H等待状态小于0,将H的等待状态设置为0;如果H不为空,获取头结点的直接后继结点(H.NEXT),如果该结点(H.NEXT)为空,则从队列尾部遍历寻找离H最近,并且不是取消状态的结点。通过LockSupport.unpark(s.thread)唤醒线程
(2)可中断:
 与不可中断类似,但在步骤2中尝试将线程挂起如果满足条件会响应中断并抛出异常
B、共享模式
    tryAcquireShared返回的不是boolean值,而是int。负数:表示获取失败;零值:表示当前结点获取成功, 但是后继结点不能再获取了;正数:表示当前结点获取成功, 并且后继结点同样可以获取成功
(1)获取锁
    如果获取成功,则将自己设置为头结点,等待状态如果是signal,此时唤醒后继结点中同样是共享模式的结点,将自己状态设置为0。等待状态如果是0(没有排队线程),则将自己状态设置为PROPAGATE,告知后面的线程可以来直接获取锁,如果是独占模式,会直接离开队列。
(2)释放锁
    释放成功会唤醒后继结点,如果自己状态为SIGNAL,则唤醒后继结点,如果不是,将自己状态设置为PROPAGATE

<think>好的,我现在需要处理用户关于AbstractQueuedSynchronizer(AQS)的深度面试题请求。首先,用户之前的问题是关于MySQL中ORDER BY和LIMIT导致的数据重复问题,我给出了解决方案和原理分析。现在用户转向Java并发机制中的AQS,并且提供了三个引用内容,但看起来这些引用可能与用户的实际需求关联不大,因为用户现在明确想要AQS的深度面试题。 首先,我需要确认用户的需求。用户提到“参考站内引用”,但给出的引用内容涉及InheritableThreadLocal、公平锁与非公平锁的tryAcquire方法,以及AQS的acquire方法。虽然这些内容与并发编程相关,但用户真正想要的是关于AQS的深度面试题,所以可能需要忽略或仅部分参考这些引用,重点放在AQS本身。 接下来,我需要回顾AQS的核心概念,确保覆盖常见的面试问题。AQS是Java并发包中锁和同步器的基础框架,使用CLH队列管理线程的排队,通过状态变量(state)实现资源的获取与释放。深度面试题通常涉及AQS的设计原理、实现细节、应用场景以及与其他并发机制的区别。 可能的面试题包括: 1. AQS的工作原理,CLH队列的实现机制。 2. 独占模式和共享模式的区别及实现。 3. 公平锁与非公平锁在AQS中的实现差异。 4. AQS中tryAcquire和tryAcquireShared方法的区别。 5. AQS如何实现可重入性,如ReentrantLock。 6. AQS中的条件变量(Condition)是如何工作的。 7. 自定义同步器时需重写哪些方法。 8. AQS中的资源状态(state)管理,如Semaphore和CountDownLatch的不同使用方式。 9. AQS与synchronized关键字的对比。 10. 实际应用中AQS的使用案例,如ReentrantReadWriteLock。 此外,用户提供的引用中有公平锁的tryAcquire方法,可以结合到问题3中,解释hasQueuedPredecessors()的作用。同时,acquire方法中的addWaiter和acquireQueued流程也是重点,需要详细解析。 需要确保每个问题的解答足够深入,涵盖实现细节和源码分析,例如CLH队列的变种、节点状态(CANCELLED、SIGNAL等)、自旋和阻塞机制等。同时,结合实际例子,如ReentrantLock或Semaphore,说明AQS的应用。 可能还需要注意用户提到的“深度面试题”,所以问题需要超出基础,涉及内部机制、性能考量、设计决策等。例如,为什么AQS选择CLH队列而不是其他结构,或者为什么非公平锁默认性能更好。 最后,检查是否有遗漏的重要知识点,如AQS中的取消机制、如何避免死锁、与Java内存模型的关系(如volatile变量的使用)。确保回答结构清晰,分点列出问题并详细解答,引用用户提供的相关引用内容适当,但以AQS为核心。</think>### AbstractQueuedSynchronizer(AQS)深度面试题解析 AQS 是 Java 并发包的核心框架,支撑了 `ReentrantLock`、`Semaphore`、`CountDownLatch` 等同步器的实现。以下从底层实现、设计思想、应用场景等维度整理深度面试题: --- ### 一、核心机制与设计 1. **AQS 的 CLH 队列是严格意义上的 CLH 锁吗?为什么?** - **答案**:AQS 的 CLH 队列是 CLH 锁的变种。原始 CLH 锁通过自旋等待前驱节点的状态,而 AQS 的 CLH 队列: - 使用双向链表实现(支持取消节点操作) - 节点状态包含 `CANCELLED`、`SIGNAL` 等标记 - 通过 `LockSupport.park/unpark` 实现线程阻塞唤醒 *差异点*:AQS 的队列更适应 Java 的线程调度模型[^3]。 2. **为什么 `tryAcquire` 和 `tryRelease` 需要开发者自定义?** - **答案**:AQS 将资源获取/释放的**策略**与**排队机制**解耦: - `tryAcquire`:定义资源获取逻辑(如独占/共享、公平/非公平) - `acquireQueued`:封装线程排队、唤醒、中断处理等通用流程 *设计思想*:模板方法模式的应用,分离变与不变的部分。 --- ### 二、源码级实现剖析 3. **公平锁与非公平锁的 `tryAcquire` 实现差异(结合引用[2])** ```java // 公平锁 tryAcquire 核心逻辑(ReentrantLock.FairSync) protected final boolean tryAcquire(int acquires) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } // 省略重入逻辑... } ``` - **关键点**: - `hasQueuedPredecessors()` 检查队列中是否有等待线程(保证先到先得) - 非公平锁直接尝试 CAS 抢占资源,不检查队列状态 *性能差异*:非公平锁减少线程切换,吞吐量更高,但可能引发线程饥饿。 4. **`acquireQueued` 方法中的自旋逻辑如何避免 CPU 空转?** - **流程解析**: 1. `addWaiter(Node.EXCLUSIVE)` 将线程包装为节点加入队列尾部 2. 自旋检查前驱节点是否为头节点且 `tryAcquire` 成功 3. 若失败,通过 `shouldParkAfterFailedAcquire` 设置前驱节点的 `SIGNAL` 状态 4. 调用 `parkAndCheckInterrupt` 挂起线程 *优化点*:通过前驱节点的状态传播,减少无效自旋[^3]。 --- ### 三、高级特性与扩展 5. **AQS 如何支持共享模式(如 Semaphore)?** - **实现对比**: - 独占模式:`acquire/release` 修改 state 为 1 或 0 - 共享模式:`acquireShared/releaseShared` 允许 state 表示多资源(如信号量许可证数量) - **传播机制**:`setHeadAndPropagate` 唤醒后续共享节点,形成级联唤醒。 6. **AQS 中的条件变量(Condition)如何与 CLH 队列交互?** - **实现原理**: - 每个 Condition 对象维护一个单向等待队列 - `await()` 将线程从 CLH 队列转移到 Condition 队列并释放锁 - `signal()` 将节点从 Condition 队列移回 CLH 队列 *注意*:一个锁可创建多个 Condition,实现精准唤醒(如生产者-消费者模型)。 --- ### 四、实战与设计思考 7. **为什么 AQS 使用 `volatile int state` 而不是原子类?** - **设计考量**: - `state` 的读写需要与 CAS 操作、队列状态变更形成内存屏障 - `volatile` 保证可见性,CAS 保证原子性(通过 `Unsafe` 类实现) *扩展问题*:AQS 如何通过 `state` 同时支持重入锁、信号量等不同组件? 8. **如何基于 AQS 实现一个自定义的同步器(如最大等待线程数限制锁)?** - **实现步骤**: 1. 继承 `AbstractQueuedSynchronizer` 2. 重写 `tryAcquire`/`tryRelease`(独占模式)或 `tryAcquireShared`/`tryReleaseShared`(共享模式) 3. 使用 `state` 记录资源状态(如当前活跃线程数) *示例代码*: ```java public class MaxConcurrentLock extends AbstractQueuedSynchronizer { private final int max; public MaxConcurrentLock(int max) { this.max = max; } protected int tryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } protected boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (compareAndSetState(current, next)) return true; } } } ``` --- ### 五、综合对比与性能优化 9. **AQS 与 `synchronized` 的关键差异** | 特性 | AQS | synchronized | |---------------------|------------------------------|---------------------------| | 实现级别 | JUC 包实现的 API 层 | JVM 内置关键字 | | 锁类型 | 支持独占、共享、条件变量 | 仅独占锁 | | 可中断性 | 支持 `lockInterruptibly()` | 阻塞时无法中断 | | 性能 | 更高灵活性,需合理使用 | 优化后的偏向锁/轻量级锁更高效 | 10. **AQS 在高并发场景下的优化策略** - **减少 CAS 竞争**:通过队列化请求降低资源争用 - **缩短临界区**:`tryAcquire` 逻辑应尽量简单 - **避免虚假唤醒**:结合 `Condition` 的精准通知机制 - **选择合适模式**:读多写少场景优先使用共享锁(如 `ReentrantReadWriteLock`) --- ### 六、进阶问题 11. **AQS 的“锁降级”是如何实现的?** - **场景**:写锁降级为读锁(保证数据可见性) - **实现**:在持有写锁时获取读锁,然后释放写锁。 *代码示例*(`ReentrantReadWriteLock`): ```java writeLock.lock(); try { // 修改数据 readLock.lock(); // 锁降级 } finally { writeLock.unlock(); } // 仍持有读锁访问数据 ``` 12. **AQS 如何处理线程取消(如 `Future.cancel`)?** - **机制**:将节点标记为 `CANCELLED`,由后续线程清理无效节点 - **关键代码**:`acquireQueued` 中通过 `cancelAcquire` 处理中断请求,调整队列指针。 --- ### 参考资料 [^1]: InheritableThreadLocal 跨线程传递数据机制 [^2]: 公平锁与非公平锁的 tryAcquire 实现差异 [^3]: AQS 的 acquire 方法实现与 CLH 队列原理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值