news 2026/9/12 13:19:52

一张图读懂 JUC 并发包:线程池、并发容器、AQS 与同步工具的 UML 全景类图解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一张图读懂 JUC 并发包:线程池、并发容器、AQS 与同步工具的 UML 全景类图解析

一张图读懂 JUC 并发包:线程池、并发容器、AQS 与同步工具的 UML 全景类图解析

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

导读

java.util.concurrent(简称 J.U.C)是 JDK 并发编程的基石,其中 ThreadPoolExecutor、ConcurrentHashMap、ReentrantLock、Semaphore、CountDownLatch、AtomicInteger 等组件几乎出现在每一段真实的高并发业务代码里。本文以 JUC 并发包 UML 全量类图 为骨架,结合本仓库docs/JDK/concurrentCoding目录下的系列源码笔记,按功能把 JUC 拆成线程池、并发容器、AQS 与锁、同步工具、原子类、并发集合六大分区逐一讲透,帮助你建立起"看到类名 → 想到继承关系 → 回忆源码实现"的完整知识地图。


一、为什么要用类图的方式学习 JUC

面对 JUC 包中上百个类,逐行读源码容易陷入"只见树木不见森林"。用类图把接口、抽象类、实现类之间的继承与组合关系可视化后,可以快速回答三个关键问题:

  1. 这个类从哪里来:例如 ThreadPoolExecutor 的继承链是ThreadPoolExecutor → AbstractExecutorService → ExecutorService → Executor,理解了这条链,就知道它为什么天然具备submit()shutdown()能力;
  2. 这个类依赖什么:例如 ReentrantLock、Semaphore、CountDownLatch 的底层同步逻辑全部委托给 AbstractQueuedSynchronizer(AQS),掌握 AQS 就等于掌握了半壁 JUC;
  3. 这个类属于哪个功能分区:JUC 的类按功能可划分为六大区域,其中线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类是重中之重。

本仓库作者的原话是:"源码看多了再去整理这个图,感觉还是很爽的……看着这些类,回想一下其中的源码实现,感觉能侃一天。"本文就把这张图拆开,逐区展开讲。


二、六大功能分区总览

根据功能,JUC 包中的类大致划分为六个部分,核心分区及其代表类如下:

功能分区代表接口 / 类职责
线程池及执行框架Executor、ExecutorService、ThreadPoolExecutor、ScheduledThreadPoolExecutor、Executors、FutureTask任务提交、线程复用、任务调度与关闭
并发容器ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList、BlockingQueue 系列队列线程安全的数据存储与传递
AQS 与锁AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、Condition同步状态管理、阻塞与唤醒、互斥与读写控制
同步工具类Semaphore、CountDownLatch、CyclicBarrier、Future / CompletableFuture多线程协作、限流、等待与汇合
原子类AtomicInteger、AtomicLong、AtomicReference、AtomicIntegerArray、字段更新器基于 CAS 的无锁原子操作
并发集合辅助ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArraySet 等有序并发容器与写时复制容器

下面逐一深入。


三、分区一:线程池及执行框架(Executor 家族)

3.1 继承链与角色划分

线程池分区的类图结构如下:

从图中可以清晰看到分层设计(对应源码笔记 Executor 线程池组件):

  • Executor 接口:最顶层的执行器,只声明一个void execute(Runnable command),含义是"在将来的某个时间执行给定的任务,任务可以在新线程、池线程或调用线程中执行";
  • ExecutorService 接口:在 Executor 基础上扩展了shutdown()优雅关闭、submit()系列提交有返回值任务的方法;
  • AbstractExecutorService 抽象类:用模板方法模式实现了submit()系列公共逻辑——将任务包装成RunnableFuture后调用尚未实现的execute(),把执行细节留给子类;
  • ThreadPoolExecutor:线程池的具体实现,内部通过ctl一个 int 同时编码线程池运行状态与工作线程数量,维护workQueuecorePoolSizemaximumPoolSizekeepAliveTimethreadFactoryhandler六大核心要素;
  • Executors:工具类,为开发者封装了newFixedThreadPool()newSingleThreadExecutor()newCachedThreadPool()等可直接使用的线程池工厂方法。

3.2 核心接口源码

public interface Executor { /** 在将来的某个时间执行给定的 Runnable,可执行于新线程、池线程或调用线程 */ void execute(Runnable command); } public interface ExecutorService extends Executor { /** 优雅关闭:继续执行完以前提交的任务,但不再接受新任务 */ void shutdown(); /** 提交有返回值的任务,返回其未来执行完成后的结果,Future.get() 将返回任务结果 */ <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); }

3.3 AbstractExecutorService 的模板方法

public abstract class AbstractExecutorService implements ExecutorService { /** 模板方法模式:execute() 来自 Executor 接口,本抽象类未实现,交由子类实现 */ public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); // 关键:此处调用的是尚未实现的 execute() return ftask; } public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; } }

3.4 ThreadPoolExecutor 的核心构造与任务执行流程

ThreadPoolExecutor 提供了多组构造方法,最终都收敛到参数最全的一个,并对参数做合法性校验:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }

各参数含义与约束如下:

参数含义说明
corePoolSize核心线程数不能为负数
maximumPoolSize最大线程数必须大于 0 且不小于 corePoolSize
keepAliveTime/unit非核心线程空闲存活时间不能为负数
workQueue任务阻塞队列不能为 null,常见实现见下文并发容器分区
threadFactory线程工厂不能为 null,默认Executors.defaultThreadFactory()
handler拒绝策略不能为 null,默认AbortPolicy(抛RejectedExecutionException

execute(Runnable command)的任务处理流程分三步,这也是面试中反复考察的经典逻辑:

public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * 1、运行的线程少于 corePoolSize → 尝试开启新线程;否则尝试进入工作队列 * 2、工作队列没满 → 进入工作队列;否则判断是否超出最大线程数 * 3、未超出最大线程数 → 尝试开启新线程;否则按饱和策略处理无法执行的任务 */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (!isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }

shutdown()则是"优雅关闭":先加mainLock锁保证线程安全,将运行状态推进到 SHUTDOWN,再中断空闲工作线程,最后尝试终止线程池——它不会中断正在执行的任务,只是不再接收新任务。

3.5 Executors 工具类与实战陷阱

public class Executors { /** 固定线程数量的线程池:核心数 = 最大数 = nThreads,无界队列 */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } /** 单线程线程池 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } /** 可缓存的弹性线程池:核心线程数 0,最大线程数 Integer.MAX_VALUE */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } }

需要特别提醒的是:newCachedThreadPool()的最大线程数是Integer.MAX_VALUE,如果任务数在某一瞬间暴涨,这个线程池很可能把服务器撑爆。这些工厂方法底层都是 new 一个 ThreadPoolExecutor,只是帮我们预先配好了参数;理解这张类图后,遇到特殊场景应直接使用 ThreadPoolExecutor 自定义参数,而不是盲目套用 Executors。


四、分区二:AQS——所有锁与同步工具的底层框架

4.1 AQS 在类图中的枢纽地位

AQS(AbstractQueuedSynchronizer)是 Doug Lea 创作的基础框架类,JUC 中 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等锁与同步工具的核心实现都依赖它。因此类图中 AQS 处于枢纽位置:锁、信号量、闭锁、栅栏等全部"挂"在它下面。

AQS 主要做三件事:

  1. 管理同步状态volatile int state);
  2. 维护同步队列(FIFO 双向链表);
  3. 阻塞和唤醒线程(基于LockSupport.park/unpark)。

从行为上区分是获取锁 / 释放锁,从模式上区分是独占锁 / 共享锁(对应源码笔记 详解 AbstractQueuedSynchronizer)。

4.2 核心数据结构:内部类 Node

当共享资源被某线程占用时,其他请求线程会被阻塞进入同步队列。AQS 的同步队列通过链表实现,载体是内部类 Node:

static final class Node { /* 标记节点在共享模式下等待 */ static final Node SHARED = new Node(); /* 标记节点在独占模式下等待 */ static final Node EXCLUSIVE = null; /* 当前线程因超时或中断被取消(终结态) */ static final int CANCELLED = 1; /* 后继线程将被阻塞,当前线程释放锁或取消后需唤醒后继(由后继线程设置前驱) */ static final int SIGNAL = -1; /* 当前线程在 condition 队列中 */ static final int CONDITION = -2; /* 用于将唤醒后继线程传递下去,完善共享锁的唤醒机制 */ static final int PROPAGATE = -3; volatile int waitStatus; // 等待状态 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 节点对应的线程 Node nextWaiter; // 等待队列中的后继节点 }

4.3 获取 / 释放锁的核心流程

获取独占锁的整体思路是"先尝试,失败则入队阻塞,被唤醒后再试":

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

acquireQueued是核心循环:只有当前驱节点是 head 时才有资格尝试获取锁;获取成功则把自己设为 head;否则根据前驱节点的 waitStatus 决定是否阻塞自己(shouldParkAfterFailedAcquire+parkAndCheckInterrupt)。入队时采用"先设置 node.prev = t 再 CAS 更新 tail"的精妙顺序,保证任意时刻 tail.prev 不为 null、队列完整。

释放独占锁的逻辑很简洁:

public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; // head 状态不会是 CANCELLED,h.waitStatus != 0 等价于 h.waitStatus < 0 if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒后继线程 return true; } return false; }

unparkSuccessor中有一个值得琢磨的细节:当node.next为 null 或已被取消时,需要从 tail 向前遍历找到 node 之后最近的非取消节点。原因是addWaiter/enq中 CAS 成功与next赋值之间存在时间窗,读到的next == null并不代表 node 就是 tail。

共享锁与独占锁的区别在于:tryAcquireShared返回负数表示获取失败,0 表示成功但后继不会成功,正数表示成功且后继争用线程也可能成功。获取成功后调用setHeadAndPropagate设置头节点并决定是否传播唤醒,配合doReleaseShared保证在 acquire 与 release 竞争的情况下,队列中等待节点始终有办法被唤醒。

4.4 AQS 的"用户视角":模板方法

AQS 通过模板方法模式向外提供服务:子类只需实现tryAcquire/tryRelease(独占模式)或tryAcquireShared/tryReleaseShared(共享模式),线程排队、阻塞唤醒、中断处理等复杂机制全部由 AQS 骨架方法完成。这也是为什么 ReentrantLock 与 Semaphore 的源码如此"薄"——重活都在 AQS 里。


五、分区三:锁组件(Lock / ReadWriteLock)

JUC 的locks包类较少,最核心的就是Lock接口、ReentrantLockReentrantReadWriteLock,其类图与主要方法如下:

5.1 Lock 接口

public interface Lock { void lock(); // 获取锁 void lockInterruptibly() throws InterruptedException; // 可中断地获取锁 boolean tryLock(); // 仅当锁空闲时才获取 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 限时获取 void unlock(); // 释放锁 }

5.2 ReentrantLock:公平与非公平

ReentrantLock 的所有方法都委托给内部同步器Sync(继承自 AQS),Sync有两个子类NonfairSyncFairSync,分别实现非公平与公平策略(对应源码笔记 Lock 锁组件):

  • 非公平锁NonfairSync.lock():上来先compareAndSetState(0, 1)抢一把,抢不到才走 AQS 排队,允许"插队";
  • 公平锁FairSync.tryAcquire():在compareAndSetState之前先调用hasQueuedPredecessors()判断队列中是否有排队者,有则让行;
  • 两种模式都支持可重入current == getExclusiveOwnerThread()时直接把 state 累加,释放时逐层递减到 0 才真正释放。
public ReentrantLock() { // 默认非公平 sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } public void lock() { sync.lock(); } public boolean tryLock() { return sync.nonfairTryAcquire(1); } public void unlock() { sync.release(1); }

5.3 ReentrantReadWriteLock:读写分离

ReentrantReadWriteLock 在单个 AQS 的state上同时编码两种计数:高 16 位记录读锁(共享)持有数,低 16 位记录写锁(独占)持有数。通过sharedCount(c)exclusiveCount(c)拆分使用:

  • tryAcquire(写锁):若读计数或写计数非 0 且持有者不是当前线程则失败;否则可重入累加;
  • tryAcquireShared(读锁):若写锁被其他线程持有则返回 -1 失败;否则 CAS 增加SHARED_UNIT并维护每个线程的读持有计数(firstReader/cachedHoldCounter/readHolds);
  • 默认new ReentrantReadWriteLock()也是非公平的,可通过fair参数指定。

读写锁适合"读多写少"场景:多个读者可并发持有读锁,写者必须独占。


六、分区四:同步工具类(Semaphore / CountDownLatch / CyclicBarrier / Future)

同步工具类用于多线程协作与流量控制,其类图位置同样挂在 AQS 之下。

6.1 Semaphore 信号量:基于 AQS 的限流器

Semaphore 可用于控制一定时间内并发执行的线程数,可应用于网关限流、资源限制(如最大可发起连接数)。由于release()释放许可时未对释放数做限制,可以通过该方法动态增加总许可数量(对应源码笔记 Semaphore)。

核心内部类Sync继承 AQS,把state直接赋值为总许可数:

abstract static class Sync extends AbstractQueuedSynchronizer { /* 赋值 state 为总许可数 */ Sync(int permits) { setState(permits); } /* 剩余许可数 */ final int getPermits() { return getState(); } /* 自旋 + CAS 非公平获取许可 */ final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } /* 自旋 + CAS 释放许可:未限制释放数,可通过 release 动态增加许可 */ protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } /* 自旋 + CAS 减少许可数量 */ final void reducePermits(int reductions) { ... } /* 丢弃所有许可 */ final int drainPermits() { ... } }

获取许可支持公平与非公平两种模式,默认非公平:

  • 公平模式:无论是否有许可,都先判断是否有线程在排队,有则进入排队,否则尝试获取许可;
  • 非公平模式:无论许可是否充足,直接尝试获取许可。

从源码可见,公平与非公平的差异就在tryAcquireShared的实现:非公平版直接自旋 CAS,公平版(FairSync)先检查hasQueuedPredecessors()

6.2 CountDownLatch / CyclicBarrier / Future 家族

  • CountDownLatch:基于 AQS 共享模式,countDown()递减 state,await()阻塞直到 state 归零,适合"等待 N 个任务完成后再继续";
  • CyclicBarrier:可循环使用的栅栏,所有线程到达屏障点后一起放行,可配合Runnable barrierAction执行汇合动作;
  • Future / CompletableFuture / FutureTask:异步任务结果载体。FutureTask实现RunnableFuture(继承 Runnable + Future),线程池的submit()正是用它包装任务;CompletableFuture实现CompletionStage,支持异步编排。

七、分区五:原子类(Atomic 家族)

原子类是基于CAS(Compare-And-Swap)+ volatile实现的无锁线程安全操作,按数据类型可分为四组:

类型代表类
基础类型AtomicInteger、AtomicLong、AtomicBoolean
引用类型AtomicReference、AtomicMarkableReference、AtomicStampedReference
数组类型AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
字段更新器AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

其中AtomicStampedReference/AtomicMarkableReference通过携带版本号或标记位解决 ABA 问题;字段更新器允许对已有类的volatile字段做原子更新而无需包装对象。在实战中,原子类常用于计数器、累加器等高频小对象场景,其性能优于加锁;而对并发写压力极大的计数场景,JDK8 还提供了LongAdder采用分段累加降低 CAS 竞争(本仓库 Sentinel 底层 LongAdder 的计数实现 有专题分析)。


八、分区六:并发容器(Concurrent 集合与阻塞队列)

并发容器是线程池工作队列与缓存系统的"弹药库",在类图中主要有两条主线:

8.1 并发 Map 与 List

  • ConcurrentHashMap:实现ConcurrentMap,JDK8 采用"数组 + 链表/红黑树 + CAS + synchronized 锁桶"的结构,读操作基本无锁,是本仓库 ConcurrentHashMap 专题 的核心主题;
  • ConcurrentSkipListMap / ConcurrentSkipListSet:基于跳表实现的有序并发容器,提供并发环境下O(log n)的范围查询;
  • CopyOnWriteArrayList / CopyOnWriteArraySet:写时复制,读操作无锁,适合"读多写极少"的场景;
  • ConcurrentLinkedQueue:基于 CAS 的无界非阻塞队列。

8.2 BlockingQueue 阻塞队列家族

BlockingQueue接口下有多个实现,各自适配不同线程池场景:

队列特性典型用途
LinkedBlockingQueue链表实现,可选有界(默认 Integer.MAX_VALUE 视为无界)Executors 的 fixed/single 线程池默认队列
ArrayBlockingQueue数组实现,有界有界任务缓冲,防止内存无限膨胀
SynchronousQueue不存储元素,直接移交Executors 的 cached 线程池默认队列
PriorityBlockingQueue支持优先级的无界阻塞队列有优先级的任务调度
DelayQueue延迟出队的无界队列定时任务、缓存过期清理
LinkedTransferQueue支持 transfer 语义的无界队列生产者直接移交消费者

阻塞队列的take()/put()基于锁与条件队列(Condition)实现,正是 AQS 的ConditionObject的典型应用。


九、结语:从类图到源码的学习路线

JUC 类图的六大分区并非孤立存在,它们以 AQS 与阻塞队列为两大枢纽彼此咬合:线程池用阻塞队列缓冲任务,用 AQS(mainLock)保护内部状态;锁与信号量直接复用 AQS 的同步队列;原子类则提供无锁的细粒度计数。建议按以下顺序结合本仓库源码笔记逐步深入:

  1. 先读 JUC 并发包 UML 全量类图,在大脑中建立分区索引;
  2. 攻克枢纽 详解 AbstractQueuedSynchronizer,理解同步队列与 acquire/release 骨架;
  3. 再看 Lock 锁组件 与 Semaphore,体会 AQS 模板方法的两种应用;
  4. 阅读 Executor 线程池组件 与 线程池组件,掌握 execute() 三步流程与 Executors 陷阱;
  5. 最后回到 JUC 并发包 UML 全量类图 中的images/JDK1.8/JUC全量UML地图.png,对照每个类回忆其源码实现,即可完成从"认识类"到"读懂源码"的闭环。

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 13:19:30

STM32定时器时钟源、PSC与ARR三大陷阱深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 13:19:08

远程智慧停车管理实战:从车牌识别到无人值守的完整指南

停车管理这个行业&#xff0c;过去十年其实是“三件套”打天下&#xff1a;保安亭、道闸杆、对讲机。一辆车进出&#xff0c;要摇窗、取卡、扫码、等抬杆&#xff0c;碰上缴费二维码模糊、手机没信号、前车磨蹭&#xff0c;后面喇叭能按成一片。尤其是夜间和恶劣天气&#xff0…

作者头像 李华
网站建设 2026/9/12 13:18:13

macOS 应用怎么选不踩坑?1 份清单说清楚

macOS 应用怎么选不踩坑&#xff1f;1 份清单说清楚 【免费下载链接】awesome-macOS  A curated list of awesome applications, softwares, tools and shiny things for macOS. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-macOS 想找一款顺手的终端…

作者头像 李华
网站建设 2026/9/12 13:15:59

无人机航拍图像地理定位建模:畸变校正与高程耦合实战

简介&#xff1a;本资源为2022年维数杯数学建模竞赛A题的完整解题支撑包&#xff0c;面向高校数学建模参赛团队、课程设计学生及自学建模的进阶学习者&#xff0c;聚焦真实场景下的多方法建模与实证分析能力训练。压缩包共10个文件&#xff0c;含2份Word文档&#xff08;逻辑回…

作者头像 李华