AbstractQueuedSynchronizer(二)

本文深入解析了AQS(AbstractQueuedSynchronizer)的工作原理及其实现机制,包括其内部的数据结构、关键方法如acquire与release的流程,以及如何通过自定义同步器实现对共享资源的管理和控制。
转载自:http://www.cnblogs.com/leesf456/p/5350186.html
              http://www.cnblogs.com/waterystone/p/4920797.html

       AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架,如ReentrantLock、ReentrantReadWriteLock、Semaphore等,它是JUC并发包中的核心基础组件。
       AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。AQS使用一个int类型的成员变量state来代表共享资源和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对state进行操作,当然AQS可以确保对state的操作是安全的。

       AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。AbstractQueuedSynchronizer类的数据结构如下:
      
       AbstractQueuedSynchronizer类底层的数据结构是使用双向链表,是队列的一种实现,故也可看成是队列,其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。

       AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():  程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):  独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):  独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):  共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):  共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
       以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
       再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。


类定义

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691L;

    protected AbstractQueuedSynchronizer() { }

    // 头结点
    private transient volatile Node head;    
    // 尾结点
    private transient volatile Node tail;    
    // 状态
    private volatile int state;    
    // 自旋时间
    static final long spinForTimeoutThreshold = 1000L;

内部类Node

static final class Node {
        // 模式,分为共享与独占
        // 共享模式
        static final Node SHARED = new Node();
        // 独占模式
        static final Node EXCLUSIVE = null;        
        // 结点状态
        // CANCELLED,值为1,表示当前的线程被取消
        // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
        // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
        // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
        // 值为0,表示当前节点在sync队列中,等待着获取锁
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;        

        // 结点状态
        volatile int waitStatus;        
        // 前驱结点
        volatile Node prev;    
        // 后继结点
        volatile Node next;        
        // 结点所对应的线程
        volatile Thread thread;        
        // 下一个等待者
        Node nextWaiter;
        
        // 结点是否在共享模式下等待
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        
        // 获取前驱结点,若前驱结点为空,抛出异常
        final Node predecessor() throws NullPointerException {
            // 保存前驱结点
            Node p = prev; 
            if (p == null) // 前驱结点为空,抛出异常
                throw new NullPointerException();
            else // 前驱结点不为空,返回
                return p;
        }
        
        // 无参构造函数
        Node() {    // Used to establish initial head or SHARED marker
        }
        
        // 构造函数
         Node(Thread thread, Node mode) {    // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        
        // 构造函数
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
       每个被阻塞的线程都会被封装成一个Node结点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态,具体状态如下。CANCELLED表示当前的线程被取消;SIGNAL表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作;CONDITION表示当前节点在等待condition,也就是在condition queue中;PROPAGATE表示当前场景下后续的acquireShared能够得以执行;值0表示当前节点在sync queue中,等待着获取锁。

acquire函数

       此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。这也正是lock()的语义,当然不仅仅只限于lock()。获取到资源后,线程就可以去执行其临界区代码了。下面是acquire()的源码:
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
       首先调用tryAcquire函数,调用此方法的线程会试图在独占模式下获取对象状态。此方法应该查询是否允许它在独占模式下获取对象状态,如果允许,则获取它并则直接返回。在AbstractQueuedSynchronizer源码中默认会抛出一个异常,即需要子类去重写此函数完成自己的逻辑。
       若tryAcquire失败,则调用addWaiter函数,addWaiter函数完成的功能是将调用此方法的线程封装成为一个结点并放入Sync queue。
       调用acquireQueued函数,此函数完成的功能是Sync queue中的结点不断尝试获取资源,若成功,则返回true,否则,返回false。

       首先分析addWaiter(Node.EXCLUSIVE):
    // 添加等待者
    private Node addWaiter(Node mode) {
        // 新生成一个结点,默认为独占模式
        Node node = new Node(Thread.currentThread(), mode);
        // 保存尾结点
        Node pred = tail;
        if (pred != null) { // 尾结点不为空,即已经被初始化
            // 将node结点的prev域连接到尾结点
            node.prev = pred; 
            if (compareAndSetTail(pred, node)) { // 比较pred是否为尾结点,是则将尾结点设置为node 
                // 设置尾结点的next域为node
                pred.next = node;
                return node; // 返回新生成的结点
            }
        }
        // 上一步失败则通过enq入队
        enq(node); // 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列
        return node;
    }
       通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了。那么该线程下一步即进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源。即acquireQueued 函数的作用:
    // sync队列中的结点在独占且忽略中断的模式下获取(资源)
    final boolean acquireQueued(final Node node, int arg) {
        // 标志
        boolean failed = true;
        try {
            // 中断标志
            boolean interrupted = false;
            for (;;) { // 自旋
                // 获取node节点的前驱结点
                final Node p = node.predecessor(); 
                if (p == head && tryAcquire(arg)) { // 前驱为头结点,说明当前节点是队列第一个等待的节点,并且成功获得锁
                    setHead(node); // 设置自己为头结点
                    p.next = null; //方便GC回收以前的head结点。
                    failed = false; // 设置标志
                    return interrupted; 
                }


                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
       首先获取当前节点的前驱节点,如果前驱节点是头结点并且能够获取资源,代表该当前节点能够占有锁,设置头结点为当前节点,返回。否则,调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt函数,shouldParkAfterFailedAcquire函数如下:
    // 当获取(资源)失败后,检查并且更新结点状态
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 获取前驱结点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1
            // 可以进行park操作
            return true; 
        if (ws > 0) { // 表示状态为CANCELLED,为1
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点,即使pred结点为正常等待状态的结点
            // 赋值pred结点的next域
            pred.next = node; 
        } else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中) 
            // 如果前驱正常,那就把前驱的状态设置成SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
        }
        // 不能进行park操作
        return false;
    }
       只有当该节点的前驱结点的状态为SIGNAL时,才可以对该结点所封装的线程进行park操作。否则,将不能进行park操作。再看parkAndCheckInterrupt函数,源码如下:
    // 进行park操作并且返回该线程是否被中断
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); //调用park()使线程进入waiting状态
        return Thread.interrupted(); // 当前线程是否已被中断,并清除中断标记位
    }
       parkAndCheckInterrupt函数里的逻辑是首先执行park操作,然后返回该线程是否已经被中断。park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:被unpark()或被interrupt()。

       再看final块中的cancelAcquire函数,其源码如下:
    // 取消继续获取(资源)
    private void cancelAcquire(Node node) {
        if (node == null)
            return;
        // 设置node结点的thread为空
        node.thread = null;

        // 保存node的前驱结点
        Node pred = node.prev;
        while (pred.waitStatus > 0) // 找到node前驱结点中第一个状态小于0的结点,即不为CANCELLED状态的结点
            node.prev = pred = pred.prev;

        // 获取pred结点的下一个结点
        Node predNext = pred.next;

        // 设置node结点的状态为CANCELLED
        node.waitStatus = Node.CANCELLED;

        if (node == tail && compareAndSetTail(node, pred)) { // node结点为尾结点,则设置尾结点为pred结点
            // 比较并设置pred结点的next节点为null
            compareAndSetNext(pred, predNext, null); 
        } else { // node结点不为尾结点,或者比较设置不成功
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) { // (pred结点不为头结点,并且pred结点的状态为SIGNAL)或者 
                                    // pred结点状态小于等于0,并且比较并设置等待状态为SIGNAL成功,并且pred结点所封装的线程不为空
                // 保存结点的后继
                Node next = node.next;
                if (next != null && next.waitStatus <= 0) // 后继不为空并且后继的状态小于等于0
                    compareAndSetNext(pred, predNext, next); // 比较并设置pred.next = next;
            } else {
                unparkSuccessor(node); // 释放node的前一个结点
            }

            node.next = node; // help GC
        }
    }
       该函数完成的功能就是取消当前线程对资源的获取,即设置该结点的状态为CANCELLED,接着再看unparkSuccessor函数,此方法用于唤醒等待队列中下一个线程,源码如下:
    // 释放后继结点
    private void unparkSuccessor(Node node) {
        // 获取node结点的等待状态
        int ws = node.waitStatus;
        if (ws < 0) // 状态值小于0,为SIGNAL -1 或 CONDITION -2 或 PROPAGATE -3
            // 比较并且设置结点等待状态,设置为0
            compareAndSetWaitStatus(node, ws, 0);

        // 获取node节点的下一个结点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) { // 下一个结点为空或者下一个节点的等待状态大于0,即为CANCELLED
            // s赋值为空
            s = null; 
            // 从尾结点开始从后往前开始遍历
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0) // 找到等待状态小于等于0的结点,找到最前的状态小于等于0的结点
                    // 保存结点
                    s = t;
        }
        if (s != null) // 该结点不为为空,唤醒
            LockSupport.unpark(s.thread);
    }

       acquire()的过程即先调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

release函数

public final boolean release(int arg) {
        if (tryRelease(arg)) { // 释放成功
            // 保存头结点
            Node h = head; 
            if (h != null && h.waitStatus != 0) // 头结点不为空并且头结点状态不为0
                unparkSuccessor(h); //释放头结点的后继结点
            return true;
        }
        return false;
    }
       tryRelease的默认实现是抛出异常,需要具体的子类实现,如果tryRelease成功,那么如果头结点不为空并且头结点的状态不为0,则释放头结点的后继结点,unparkSuccessor函数已经分析过,不再累赘。
       对于其他函数我们也可以分析,与前面分析的函数大同小异,所以,不再累赘。

acquireShared函数

       此方法是共享模式下线程获取共享资源的顶层入口。它会获取指定量的资源,获取成功则直接返回,获取失败则进入等待队列,直到获取到资源为止,整个过程忽略中断。下面是acquireShared()的源码:
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
       这里tryAcquireShared()依然需要自定义同步器去实现。但是AQS已经把其返回值的语义定义好了:负值代表获取失败;0代表获取成功,但没有剩余资源;正数表示获取成功,还有剩余资源,其他线程还可以去获取。
       所以这里acquireShared()的流程就是:tryAcquireShared()尝试获取资源,成功则直接返回;失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。

releaseShared函数

       此方法是共享模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果成功释放且允许唤醒等待线程,它会唤醒等待队列里的其他线程来获取资源。下面是releaseShared()的源码:
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//尝试释放资源
        doReleaseShared();//唤醒后继结点
        return true;
    }
    return false;
}
       此方法的流程也比较简单,一句话:释放掉资源后,唤醒后继。跟独占模式下的release()相似,但有一点稍微需要注意:独占模式下的tryRelease()在完全释放掉资源(state=0)后,才会返回true去唤醒其他线程,这主要是基于独占下可重入的考量;而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制一定量的线程并发执行,那么拥有资源的线程在释放掉部分资源时就可以唤醒后继等待结点。


AbstractQueuedSynchronizer中还有内部类ConditionObject,表示Condition Queue的数据结构,由于篇幅原因暂不展开了。



参考阅读: http://blog.youkuaiyun.com/zero__007/article/details/51570276




<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 队列原理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值