JUC之AQS源码分析

队列同步器(AbstractQueuedSynchronizer)作为构建锁及其他同步器的基础框架,它通过volatile状态变量及FIFO队列实现线程获取或等待资源的逻辑,JUC中的同步组件(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等)均基于此基础上实现。
AbstractQueuedSynchronizer的源码中,定义如下模板方法:
这里写图片描述
通过上述定义的模板办法,子类继承AbstractQueuedSynchronizer之后,根据特定需求,实现模板方法中调用的抽象方法,即可定制相应的同步框架,相应的抽象方法如下:
这里写图片描述

在AQS的模型中,将线程包装为Node(节点),节点的重要成员变量说明:
这里写图片描述

waitStatus值: =1节点设置为取消(超时或中断);
=-1后继节点处于等待
=-2节点位于等待队列
=-3下次获取共享式同步状态被传播
接下来,逐步分析AQS定义的同步方法

一、独占锁的获取与是释放

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

调用抽象tryAcquire(arg)尝试获取资源,获取失败则执行addWaiter(Node.EXCLUSIVE)将线程加入FIFO队列

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//以独占模式封装线程的Node节点
    Node pred = tail;//记录尾节点
    if (pred != null) {//尾节点存在
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//CAS将node设置为尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node);//尾节点不存在或CAS node尾尾节点失败
    return node;
}
private Node enq(final Node node) {
    for (;;) { 
        Node t = tail;//获取当前为节点
        if (t == null) { // 队列为空,构建头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {// CAS将节点设置为尾节点
                t.next = node;
                return t;
            }
        }
    }
}

将node添加至同步对列之后,执行acquireQueued(final Node node, int arg)线程进入自旋状态,,每个节点都在观察,当条件满足时,获取同步状态;否则,节点阻塞

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

综上分析,独占式同步状态获取流程如下图:
这里写图片描述
独占式锁的释放,无多线程竞争,逻辑相当简单,只需调用抽象方法,并唤醒后继节

public final boolean release(int arg) {
    if (tryRelease(arg)) {//调用抽象方法
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒后继节点的阻塞线程
        return true;
    }
    return false;
}

二、共享锁的获取与释放

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)//获取失败,自旋获取同步状态
        doAcquireShared(arg);
}

在共享式获取中,先尝试调用抽象方法获取锁,失败,则自旋获取

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//接入FIFO同步队列
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//前驱节点
            if (p == head) {//前驱节点为头节点
                int r = tryAcquireShared(arg);
                if (r >= 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);
    }
}

共享式获取流程与独占式获取大体一致,只是具体获取同步状态的规则不一致(由抽象方法实现),然而共享式释放同步资源则与独占释放不那么一样(主要在于共享式释放存在多线程竞争)

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//抽象方法获取资源
        doReleaseShared();//
        return true;
    }
    return false;
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {//同步队列含有等待节点
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//设置当前线程状态
                    continue;            // loop to recheck cases
                unparkSuccessor(h);//唤醒后继线程
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//设置当前线程
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

无论是共享式还是独占式获取,线程自旋观察过程中是否阻塞if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())由此实现

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)//前驱节点状态=-1需要阻塞
        return true;
    if (ws > 0) {//节点因中断或超时被取消CACLLED
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);//跳过被取消的节点
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置有效前节点状态
    }
    return false;
}

在shouldParkAfterFailedAcquire根据前节点状态来判断是否需要阻塞当前线程,然后执行parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//阻塞当前线程
    return Thread.interrupted();//返回中断状态
}

共享式与独占式获取同步状态的流程以介绍未必,至于对应超时,及中断响应获取的状态的流程与上大体一致,只是在定义模板方法方法中添加了响应中断、超时等业务,具体并在一一分析。

<think>嗯,用户之前让我介绍过Java多线程的基础知识,现在他们想深入了解JUC(java.util.concurrent)的源码。这说明用户可能已经掌握了多线程的基本概念和使用方法,现在想更深入理解底层实现,提升自己的技术水平。 首先,我需要确定用户对JUC的了解程度。他们可能已经用过一些工具类,比如ReentrantLock或CountDownLatch,但现在想了解这些类内部的机制。用户可能是中高级开发者,希望优化代码性能,或者准备面试,需要深入理解并发框架。 接下来,我需要考虑如何组织内容。直接深入源码可能会让用户感到困惑,所以应该先从JUC的整体结构入手,然后分模块解析核心类。比如,原子类、锁、同步器、并发集合、线程池等,每个部分都需要挑选关键点进行讲解。 用户可能需要知道关键设计模式,比如AQS(AbstractQueuedSynchronizer),因为这是JUC的核心。需要详细解释AQS的工作原理,如CLH队列、状态管理等。此外,CAS操作和volatile关键字也是实现无锁并发的基础,需要结合源码说明它们的作用。 另外,用户可能对常见类的实现感兴趣,比如ReentrantLock的公平锁与非公平锁区别,ConcurrentHashMap的分段锁或CAS优化,线程池的任务调度流程等。需要结合代码示例和流程图,帮助用户理解这些复杂机制。 同时,要注意避免过于冗长的代码展示,而是聚焦在关键方法和设计思路上。例如,AQS的acquire和release方法,以及它们如何被ReentrantLock或Semaphore调用。此外,可以比较不同版本的实现变化,比如JDK1.7和1.8中ConcurrentHashMap的改进,让用户了解优化方向。 最后,需要给出学习建议,推荐用户如何逐步深入源码,比如从简单的AtomicInteger开始,再逐步研究AQS和锁的实现。同时,提醒用户注意调试和测试的重要性,通过实际案例加深理解。 总结下来,回答的结构应该是先概述JUC的重要性,再分模块解析核心源码,结合关键设计模式和具体类实现,最后给出学习方法和资源推荐。这样既系统又深入,帮助用户由浅入深地掌握JUC源码。</think>好的!`java.util.concurrent`(JUC)是 Java 并发编程的核心工具包,包含大量高性能、线程安全的工具类和框架。深入源码理解其设计思想,能帮助你掌握高并发编程的精髓。以下从 **核心模块**、**设计模式**、**关键类源码分析** 三个维度进行详细解读。 --- ### **一、JUC 整体架构** JUC 的核心模块包括: 1. **原子类**(Atomic):基于 CAS 的无锁并发操作(如 `AtomicInteger`)。 2. **锁与同步器**(Locks):如 `ReentrantLock`、`AQS`(AbstractQueuedSynchronizer)。 3. **并发容器**(Collections):如 `ConcurrentHashMap`、`CopyOnWriteArrayList`。 4. **线程池**(Executors):`ThreadPoolExecutor`、`ForkJoinPool`。 5. **同步工具**(Tools):`CountDownLatch`、`CyclicBarrier`、`Semaphore`。 6. **并发流与 Future**:如 `CompletableFuture`、`ForkJoinTask`。 --- ### **二、核心源码解读** #### **1. AQS(AbstractQueuedSynchronizer)** AQSJUC 锁和同步器的基石(如 `ReentrantLock`、`Semaphore` 均基于它实现)。 ##### **核心设计** - **状态变量(state)**:通过 `volatile int state` 表示资源状态(如锁的持有次数)。 - **CLH 队列**:一个双向链表实现的等待队列,存储竞争资源的线程。 - **模板方法模式**:子类只需实现 `tryAcquire`/`tryRelease`,AQS 负责线程排队和唤醒。 ##### **源码关键方法** ```java // 独占模式获取资源(如 ReentrantLock.lock()) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 子类需实现 tryAcquire(如判断 state 是否为0) protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } ``` ##### **实现示例:ReentrantLock 的公平锁** ```java // 公平锁的 tryAcquire 实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 关键:检查队列是否有等待线程 compareAndSetState(0, acquires)) { // CAS 更新状态 setExclusiveOwnerThread(current); return true; } } // 处理重入逻辑(state 增加) else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; setState(nextc); return true; } return false; } ``` --- #### **2. 原子类(AtomicInteger)** 原子类的核心是 **CAS(Compare-And-Swap)** 操作,直接依赖 CPU 指令(如 x86 的 `cmpxchg`)。 ##### **源码关键实现** ```java public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value; // 通过 volatile 保证可见性 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } } // Unsafe 类的 CAS 操作(native 方法) public final class Unsafe { public final native boolean compareAndSwapInt( Object o, long offset, int expected, int x); } ``` ##### **CAS 的 ABA 问题** - **问题**:变量从 A 改为 B 又改回 A,CAS 无法感知中间变化。 - **解决方案**:使用 `AtomicStampedReference` 添加版本号。 --- #### **3. ConcurrentHashMap(JDK 1.8)** JDK 1.8 摒弃分段锁,改用 **CAS + synchronized** 实现更细粒度的锁。 ##### **关键设计** - **Node 数组(table)**:每个桶位(bucket)是一个链表或红黑树。 - **并发控制**: - **CAS 初始化数组**:避免多线程重复初始化。 - **synchronized 锁桶头节点**:仅锁定单个桶,减少竞争。 ##### **putVal 方法核心逻辑** ```java final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); // CAS 初始化数组 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // CAS 插入新节点 } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); // 协助扩容 else { synchronized (f) { // 锁住桶头节点 // 插入链表或红黑树 } } } } ``` --- #### **4. ThreadPoolExecutor** 线程池的核心是 **任务队列** 和 **线程调度策略**。 ##### **核心参数** - **corePoolSize**:核心线程数(即使空闲也不销毁)。 - **workQueue**:任务队列(如 `LinkedBlockingQueue`)。 - **RejectedExecutionHandler**:拒绝策略(如丢弃、抛异常)。 ##### **execute 方法逻辑** ```java public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) // 创建新线程执行任务 return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { // 任务入队 // 二次检查线程池状态 } else if (!addWorker(command, false)) // 尝试创建非核心线程 reject(command); // 触发拒绝策略 } ``` --- ### **三、JUC 设计模式与技巧** 1. **CAS 无锁编程** - 通过 `Unsafe` 类直接操作内存,避免锁竞争(如原子类、ConcurrentHashMap)。 - 适用场景:读多写少,竞争不激烈。 2. **模板方法模式** - AQS 将公共逻辑(如线程排队)抽象到基类,子类只需实现特定方法(如 `tryAcquire`)。 3. **写时复制(Copy-On-Write)** - `CopyOnWriteArrayList` 在修改时复制整个数组,保证读操作无锁。 4. **分段锁优化** - JDK 1.7 的 `ConcurrentHashMap` 使用分段锁(Segment),减少锁粒度。 --- ### **四、高频源码面试题** 1. **AQS 的 CLH 队列为什么是双向链表?** - 答:为了支持取消操作(如超时或中断),需要快速定位前驱节点。 2. **ConcurrentHashMap 1.7 和 1.8 的区别?** - 1.7:分段锁 + ReentrantLock; - 1.8:CAS + synchronized + 红黑树。 3. **线程池如何回收空闲线程?** - 答:`ThreadPoolExecutor` 通过 `getTask()` 中的 `poll(keepAliveTime)` 实现超时回收。 --- ### **五、学习建议** 1. **调试源码**:通过 IDE 调试 `ReentrantLock.lock()` 的执行流程。 2. **阅读注释**:JUC 类的源码注释通常包含设计思路和并发规则。 3. **手写简化版**:尝试实现一个简单的 AQS 或线程池。 理解 JUC 源码不仅能提升并发编程能力,还能深入掌握 Java 的高性能设计哲学。建议从 `AtomicInteger` 和 `ReentrantLock` 入手,逐步扩展到复杂类如 `ConcurrentHashMap` 和 `ThreadPoolExecutor`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值