别只盯着 AQS 锁了G1 与 ZGC 才是并发卡顿的“隐形杀手”前言上周三凌晨两点我被电话吵醒了。线上核心接口响应时间突然飙升从 50ms 涨到了 2s。监控面板上CPU 使用率只有 30%内存也没爆。乍一看这简直是“灵异事件”。我连上服务器先查了线程dump。发现大量线程卡在ReentrantLock的acquire方法上。很多人第一反应是死锁或者锁竞争太激烈。但仔细看堆栈它们其实是在等 GC 释放资源。这就是典型的“锁竞争”与“垃圾回收”的恶性耦合。你以为是锁的问题其实是 GC 在背后拖了后腿。今天咱们不聊虚的直接拆解 AQS 队列机制顺便把 G1 和 ZGC 的底裤都扒干净。让你明白为什么有时候加锁反而让系统更慢。一、底层原理1.1 核心机制先说 AQSAbstractQueuedSynchronizer。它其实是 JDK 并发包的“地基”。你用的ReentrantLock、CountDownLatch底层全是它。它的核心就两件事状态管理 队列排队。状态用一个volatile int表示比如 0 代表没锁1 代表有锁。队列是个 FIFO 的 CLH 变体队列。怎么理解这个队列想象早高峰的地铁站。想进闸机获取锁的人如果闸机被占了state1你就得去排队入队。排在前面的人进去了闸机释放后面的人才能动。AQS 里这个排队过程是通过Node节点挂起线程实现的。一旦获取锁失败线程就进入WAITING状态不占 CPU。等到前一个节点释放锁会唤醒后继节点。这个唤醒过程就是LockSupport.unpark。这里有个关键点GC 什么时候介入当线程被挂起时它持有的对象引用可能变成垃圾。如果此时触发 Full GCSTWStop-The-World会让所有线程暂停。包括那些正在排队等锁的线程。这就导致锁释放延迟排队队伍越来越长。G1 和 ZGC 就是为了解决这个“停顿”而生的。G1 把堆内存切分成一个个 Region。它不像 CMS 那样整个堆一起扫而是优先回收垃圾最多的 Region。这就叫“可预测的停顿模型”。你可以设定最大停顿时间比如 200ms。G1 会尽量在这个时间内干完活。ZGC 更狠它直接上了读屏障。对象引用被读取时如果指向的对象被移动了CPU 会帮忙修正地址。这意味着 ZGC 几乎不需要 STW。哪怕堆内存有 100GB停顿时间也能控制在 1ms 以内。这就极大减少了锁竞争线程被“冻住”的概率。下面这张图把 AQS 队列和 GC 停顿的关系画出来了。graph TD A[线程尝试获取锁] -- B{状态 state 是否为 0?} B -- 是 -- C[CAS 修改 state 为 1] C -- D[获取锁成功] B -- 否 -- E[封装 Node 节点入队] E -- F[LockSupport.park 挂起线程] F -- G[等待 GC 或 锁释放] G -- H{触发 GC 停顿?} H -- 是 -- I[所有线程暂停 STW] I -- J[锁释放延迟] J -- K[队列积压 响应变慢] H -- 否 -- L[前驱节点释放锁] L -- M[unpark 后继线程] M -- N[重新尝试 CAS] N -- B style A fill:#f9f,stroke:#333 style K fill:#f96,stroke:#333 style I fill:#f00,stroke:#333,color:#fff1.2 与同类方案的对比光说 AQS 不够咱们看看它和另外两种同步机制的区别。synchronized是 JVM 层面的AQS 是 JDK 层面的。StampedLock是 AQS 的变种支持乐观读。特性synchronizedAQS (ReentrantLock)StampedLock实现层级JVM 内置JDK 代码实现JDK 代码实现锁公平性非公平可配置 (默认非公平)非公平条件变量wait/notifyCondition (更灵活)不支持性能表现JDK6 后优化好高竞争下略优读多写少场景极佳GC 友好度依赖 JVM 优化依赖底层 GC 策略依赖底层 GC 策略你会发现AQS 的优势在于“灵活”。它能让你自定义同步逻辑。但代价是你必须自己管理锁的粒度。如果锁住的范围太大GC 扫描根对象时压力会倍增。二、快速上手咱们先写个最简单的 AQS 自定义锁。别小看这个很多中间件的消息队列锁定就是这么写的。目标是实现一个能在 1 秒内自动超时释放的锁。import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * 自定义超时锁演示 * 模拟生产环境中防止线程无限等待的场景 */ public class TimeoutLock { // 内部类继承 AQS负责管理锁状态 private static class Sync extends AbstractQueuedSynchronizer { // 尝试获取锁返回 true 表示成功 Override protected boolean tryAcquire(int arg) { // 核心逻辑CAS 修改 state0 变 1 if (compareAndSetState(0, 1)) { // 设置独占线程 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // 尝试释放锁 Override protected boolean tryRelease(int arg) { // 必须检查是不是当前线程在释放防止误操作 if (getExclusiveOwnerThread() ! Thread.currentThread()) { throw new IllegalMonitorStateException(非法释放锁); } // 重置状态 setState(0); setExclusiveOwnerThread(null); return true; } } private final Sync sync new Sync(); /** * 获取锁带超时时间 * param timeout 等待时长 * param unit 时间单位 * return 是否获取成功 */ public boolean tryLock(long timeout, TimeUnit unit) { // 调用 AQS 的框架方法它会自动处理排队和挂起 return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.release(1); } }这段代码只有 50 行但把 AQS 的骨架都搭好了。tryAcquire和tryRelease是模板方法。子类只需要实现具体的业务逻辑。AQS 框架会帮你处理排队、挂起、唤醒这些脏活累活。三、核心 API / 深水区3.1 核心方法速查玩 AQS这几个方法必须滚瓜烂熟。方法名作用适用场景tryAcquire(int arg)尝试同步获取实现独占锁tryRelease(int arg)尝试同步释放实现独占锁tryAcquireShared(int arg)尝试共享获取实现读写锁、信号量tryReleaseShared(int arg)尝试共享释放实现读写锁、信号量isHeldExclusively()判断当前线程是否持有锁辅助 Condition 实现3.2 生产级配置在生产环境用 G1 或 ZGC参数配置直接决定生死。别用默认配置默认是给 Demo 用的。G1 推荐配置# 堆内存设置 -Xms4g -Xmx4g # 最大停顿时间目标G1 会尽力满足 -XX:MaxGCPauseMillis200 # 启动混合收集回收老年代 -XX:UseG1GC # 日志方便排查 STW 时间 -Xlog:gc*:file/tmp/gc.log:time,uptime,level,tagsZGC 推荐配置# JDK 15 直接启用 -XX:UseZGC # 开启分代回收 (JDK 19)性能更好 -XX:ZGenerational # 堆内存可以设大点ZGC 扛得住 -Xms8g -Xmx8g3.3 高级定制如果你发现 G1 的停顿还是不可控可以调整InitiatingHeapOccupancyPercent。默认是 45%意思是堆占用超过 45% 就开始并发标记。调低这个值比如 30%会让 GC 更早介入。虽然 CPU 会稍微高一点但能避免突发的大停顿。这就好比扫地别等屋子全脏了再扫每天扫一点心里不慌。四、实战演练咱们来模拟一个真实场景。高并发下多个线程竞争同一个资源同时后台触发 GC。看看 AQS 队列会怎么变化。import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class GcLockStressTest { // 自定义锁 private static final TimeoutLock lock new TimeoutLock(); // 计数器模拟业务处理 private static final AtomicInteger successCount new AtomicInteger(0); // 失败计数器模拟超时 private static final AtomicInteger timeoutCount new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { // 创建线程池模拟 50 个并发用户 ExecutorService executor Executors.newFixedThreadPool(50); System.out.println(开始压力测试模拟 GC 干扰场景...); for (int i 0; i 50; i) { executor.submit(() - { try { // 尝试获取锁最多等 500 毫秒 // 如果 GC 停顿超过这个时间这里就会超时 if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { try { // 模拟业务逻辑耗时 10ms Thread.sleep(10); successCount.incrementAndGet(); } finally { lock.unlock(); } } else { // 获取锁失败记录日志 timeoutCount.incrementAndGet(); System.out.println(线程 Thread.currentThread().getName() 获取锁超时); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // 主线程休眠 2 秒让子线程跑一会儿 Thread.sleep(2000); // 手动触发 GC模拟生产环境的大对象分配 System.gc(); System.out.println(已触发 System.gc()观察锁竞争情况); Thread.sleep(3000); executor.shutdown(); System.out.println( 测试结束 ); System.out.println(成功处理: successCount.get()); System.out.println(超时失败: timeoutCount.get()); } }运行这段代码你会发现。如果不用 ZGCtimeoutCount可能会飙升。因为System.gc()触发的 STW 可能长达几百毫秒。所有排队线程都被冻住等 GC 结束超时时间早过了。换成 ZGC 后timeoutCount会显著下降。因为 GC 停顿被压缩到了毫秒级锁释放足够快。五、避坑指南与最佳实践踩过的坑都给你总结在这儿了。技巧 1锁粒度要细别把整个方法都锁住。只锁共享变量访问的那几行代码。锁住的时间越短GC 造成的影响就越小。⚠️警告 1慎用System.gc()除非你非常清楚自己在做什么。否则千万别在生产环境手动调这个。它相当于强制物业把整栋楼断电打扫。✅推荐 1监控 GC 停顿时间用 Prometheus Grafana 监控JVM_GC_Pause_Time。如果这个指标和接口延迟曲线高度重合。那不用怀疑就是 GC 在搞鬼。技巧 2大对象直接进老年代G1 里大对象会直接进老年代。如果老年代回收慢停顿就长。尽量优化代码减少大对象创建。比如别在循环里new大数组。⚠️警告 2ZGC 的 CPU 开销ZGC 虽然停顿短但平时 CPU 占用比 G1 高。大概高 10% 左右。如果机器 CPU 本来就紧缺得慎重。✅推荐 2混合使用锁与无锁能不用锁就不用锁。比如用AtomicInteger代替synchronized计数。无锁并发GC 怎么扫都跟你没关系。六、综合实战演示最后咱们写个完整的示例。结合 AQS 和 ZGC 配置做一个高可用的资源池。import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * 高并发资源池示例 * 结合 AQS 锁与 GC 优化策略 */ public class ResilientResourcePool { private final TimeoutLock lock new TimeoutLock(); private final AtomicInteger availableResources new AtomicInteger(10); private static final int MAX_RETRIES 3; /** * 获取资源带重试机制 * 防止因 GC 停顿导致的单次超时 */ public boolean acquireResource(long timeoutMs) { for (int i 0; i MAX_RETRIES; i) { try { // 尝试获取锁 if (lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) { try { // 检查资源是否充足 if (availableResources.get() 0) { availableResources.decrementAndGet(); return true; } } finally { lock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } // 如果因为 GC 停顿超时稍微等一会儿再试 if (i MAX_RETRIES - 1) { try { Thread.sleep(50); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } return false; } /** * 释放资源 */ public void releaseResource() { // 简单模拟实际生产需加锁保证原子性 availableResources.incrementAndGet(); } public static void main(String[] args) { // 模拟 JVM 启动参数-XX:UseZGC -Xms2g -Xmx2g System.out.println(当前 GC 策略: java.lang.management.ManagementFactory.getGarbageCollectorMXBeans()); ResilientResourcePool pool new ResilientResourcePool(); ExecutorService executor Executors.newCachedThreadPool(); for (int i 0; i 20; i) { executor.submit(() - { if (pool.acquireResource(1000)) { System.out.println(Thread.currentThread().getName() 获取资源成功); pool.releaseResource(); } else { System.out.println(Thread.currentThread().getName() 获取资源失败); } }); } executor.shutdown(); } }这段代码里acquireResource加了重试。哪怕第一次因为 GC 停顿超时第二次还有机会。这就是生产级代码该有的韧性。七、总结AQS 是并发控制的骨架GC 是内存管理的血液。两者在运行时紧密交织。锁竞争会让线程挂起GC 停顿会让挂起的线程无法醒来。想解决卡顿不能只调锁得盯着 GC 日志看。G1 适合大堆、可预测停顿。ZGC 适合超大堆、极低延迟。选对工具配合合理的锁粒度。你的系统才能在并发高压下依然稳如老狗。
别只盯着 AQS 锁了!G1 与 ZGC 才是并发卡顿的“隐形杀手”
发布时间:2026/6/3 1:53:07
别只盯着 AQS 锁了G1 与 ZGC 才是并发卡顿的“隐形杀手”前言上周三凌晨两点我被电话吵醒了。线上核心接口响应时间突然飙升从 50ms 涨到了 2s。监控面板上CPU 使用率只有 30%内存也没爆。乍一看这简直是“灵异事件”。我连上服务器先查了线程dump。发现大量线程卡在ReentrantLock的acquire方法上。很多人第一反应是死锁或者锁竞争太激烈。但仔细看堆栈它们其实是在等 GC 释放资源。这就是典型的“锁竞争”与“垃圾回收”的恶性耦合。你以为是锁的问题其实是 GC 在背后拖了后腿。今天咱们不聊虚的直接拆解 AQS 队列机制顺便把 G1 和 ZGC 的底裤都扒干净。让你明白为什么有时候加锁反而让系统更慢。一、底层原理1.1 核心机制先说 AQSAbstractQueuedSynchronizer。它其实是 JDK 并发包的“地基”。你用的ReentrantLock、CountDownLatch底层全是它。它的核心就两件事状态管理 队列排队。状态用一个volatile int表示比如 0 代表没锁1 代表有锁。队列是个 FIFO 的 CLH 变体队列。怎么理解这个队列想象早高峰的地铁站。想进闸机获取锁的人如果闸机被占了state1你就得去排队入队。排在前面的人进去了闸机释放后面的人才能动。AQS 里这个排队过程是通过Node节点挂起线程实现的。一旦获取锁失败线程就进入WAITING状态不占 CPU。等到前一个节点释放锁会唤醒后继节点。这个唤醒过程就是LockSupport.unpark。这里有个关键点GC 什么时候介入当线程被挂起时它持有的对象引用可能变成垃圾。如果此时触发 Full GCSTWStop-The-World会让所有线程暂停。包括那些正在排队等锁的线程。这就导致锁释放延迟排队队伍越来越长。G1 和 ZGC 就是为了解决这个“停顿”而生的。G1 把堆内存切分成一个个 Region。它不像 CMS 那样整个堆一起扫而是优先回收垃圾最多的 Region。这就叫“可预测的停顿模型”。你可以设定最大停顿时间比如 200ms。G1 会尽量在这个时间内干完活。ZGC 更狠它直接上了读屏障。对象引用被读取时如果指向的对象被移动了CPU 会帮忙修正地址。这意味着 ZGC 几乎不需要 STW。哪怕堆内存有 100GB停顿时间也能控制在 1ms 以内。这就极大减少了锁竞争线程被“冻住”的概率。下面这张图把 AQS 队列和 GC 停顿的关系画出来了。graph TD A[线程尝试获取锁] -- B{状态 state 是否为 0?} B -- 是 -- C[CAS 修改 state 为 1] C -- D[获取锁成功] B -- 否 -- E[封装 Node 节点入队] E -- F[LockSupport.park 挂起线程] F -- G[等待 GC 或 锁释放] G -- H{触发 GC 停顿?} H -- 是 -- I[所有线程暂停 STW] I -- J[锁释放延迟] J -- K[队列积压 响应变慢] H -- 否 -- L[前驱节点释放锁] L -- M[unpark 后继线程] M -- N[重新尝试 CAS] N -- B style A fill:#f9f,stroke:#333 style K fill:#f96,stroke:#333 style I fill:#f00,stroke:#333,color:#fff1.2 与同类方案的对比光说 AQS 不够咱们看看它和另外两种同步机制的区别。synchronized是 JVM 层面的AQS 是 JDK 层面的。StampedLock是 AQS 的变种支持乐观读。特性synchronizedAQS (ReentrantLock)StampedLock实现层级JVM 内置JDK 代码实现JDK 代码实现锁公平性非公平可配置 (默认非公平)非公平条件变量wait/notifyCondition (更灵活)不支持性能表现JDK6 后优化好高竞争下略优读多写少场景极佳GC 友好度依赖 JVM 优化依赖底层 GC 策略依赖底层 GC 策略你会发现AQS 的优势在于“灵活”。它能让你自定义同步逻辑。但代价是你必须自己管理锁的粒度。如果锁住的范围太大GC 扫描根对象时压力会倍增。二、快速上手咱们先写个最简单的 AQS 自定义锁。别小看这个很多中间件的消息队列锁定就是这么写的。目标是实现一个能在 1 秒内自动超时释放的锁。import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * 自定义超时锁演示 * 模拟生产环境中防止线程无限等待的场景 */ public class TimeoutLock { // 内部类继承 AQS负责管理锁状态 private static class Sync extends AbstractQueuedSynchronizer { // 尝试获取锁返回 true 表示成功 Override protected boolean tryAcquire(int arg) { // 核心逻辑CAS 修改 state0 变 1 if (compareAndSetState(0, 1)) { // 设置独占线程 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // 尝试释放锁 Override protected boolean tryRelease(int arg) { // 必须检查是不是当前线程在释放防止误操作 if (getExclusiveOwnerThread() ! Thread.currentThread()) { throw new IllegalMonitorStateException(非法释放锁); } // 重置状态 setState(0); setExclusiveOwnerThread(null); return true; } } private final Sync sync new Sync(); /** * 获取锁带超时时间 * param timeout 等待时长 * param unit 时间单位 * return 是否获取成功 */ public boolean tryLock(long timeout, TimeUnit unit) { // 调用 AQS 的框架方法它会自动处理排队和挂起 return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public void unlock() { sync.release(1); } }这段代码只有 50 行但把 AQS 的骨架都搭好了。tryAcquire和tryRelease是模板方法。子类只需要实现具体的业务逻辑。AQS 框架会帮你处理排队、挂起、唤醒这些脏活累活。三、核心 API / 深水区3.1 核心方法速查玩 AQS这几个方法必须滚瓜烂熟。方法名作用适用场景tryAcquire(int arg)尝试同步获取实现独占锁tryRelease(int arg)尝试同步释放实现独占锁tryAcquireShared(int arg)尝试共享获取实现读写锁、信号量tryReleaseShared(int arg)尝试共享释放实现读写锁、信号量isHeldExclusively()判断当前线程是否持有锁辅助 Condition 实现3.2 生产级配置在生产环境用 G1 或 ZGC参数配置直接决定生死。别用默认配置默认是给 Demo 用的。G1 推荐配置# 堆内存设置 -Xms4g -Xmx4g # 最大停顿时间目标G1 会尽力满足 -XX:MaxGCPauseMillis200 # 启动混合收集回收老年代 -XX:UseG1GC # 日志方便排查 STW 时间 -Xlog:gc*:file/tmp/gc.log:time,uptime,level,tagsZGC 推荐配置# JDK 15 直接启用 -XX:UseZGC # 开启分代回收 (JDK 19)性能更好 -XX:ZGenerational # 堆内存可以设大点ZGC 扛得住 -Xms8g -Xmx8g3.3 高级定制如果你发现 G1 的停顿还是不可控可以调整InitiatingHeapOccupancyPercent。默认是 45%意思是堆占用超过 45% 就开始并发标记。调低这个值比如 30%会让 GC 更早介入。虽然 CPU 会稍微高一点但能避免突发的大停顿。这就好比扫地别等屋子全脏了再扫每天扫一点心里不慌。四、实战演练咱们来模拟一个真实场景。高并发下多个线程竞争同一个资源同时后台触发 GC。看看 AQS 队列会怎么变化。import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class GcLockStressTest { // 自定义锁 private static final TimeoutLock lock new TimeoutLock(); // 计数器模拟业务处理 private static final AtomicInteger successCount new AtomicInteger(0); // 失败计数器模拟超时 private static final AtomicInteger timeoutCount new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { // 创建线程池模拟 50 个并发用户 ExecutorService executor Executors.newFixedThreadPool(50); System.out.println(开始压力测试模拟 GC 干扰场景...); for (int i 0; i 50; i) { executor.submit(() - { try { // 尝试获取锁最多等 500 毫秒 // 如果 GC 停顿超过这个时间这里就会超时 if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { try { // 模拟业务逻辑耗时 10ms Thread.sleep(10); successCount.incrementAndGet(); } finally { lock.unlock(); } } else { // 获取锁失败记录日志 timeoutCount.incrementAndGet(); System.out.println(线程 Thread.currentThread().getName() 获取锁超时); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // 主线程休眠 2 秒让子线程跑一会儿 Thread.sleep(2000); // 手动触发 GC模拟生产环境的大对象分配 System.gc(); System.out.println(已触发 System.gc()观察锁竞争情况); Thread.sleep(3000); executor.shutdown(); System.out.println( 测试结束 ); System.out.println(成功处理: successCount.get()); System.out.println(超时失败: timeoutCount.get()); } }运行这段代码你会发现。如果不用 ZGCtimeoutCount可能会飙升。因为System.gc()触发的 STW 可能长达几百毫秒。所有排队线程都被冻住等 GC 结束超时时间早过了。换成 ZGC 后timeoutCount会显著下降。因为 GC 停顿被压缩到了毫秒级锁释放足够快。五、避坑指南与最佳实践踩过的坑都给你总结在这儿了。技巧 1锁粒度要细别把整个方法都锁住。只锁共享变量访问的那几行代码。锁住的时间越短GC 造成的影响就越小。⚠️警告 1慎用System.gc()除非你非常清楚自己在做什么。否则千万别在生产环境手动调这个。它相当于强制物业把整栋楼断电打扫。✅推荐 1监控 GC 停顿时间用 Prometheus Grafana 监控JVM_GC_Pause_Time。如果这个指标和接口延迟曲线高度重合。那不用怀疑就是 GC 在搞鬼。技巧 2大对象直接进老年代G1 里大对象会直接进老年代。如果老年代回收慢停顿就长。尽量优化代码减少大对象创建。比如别在循环里new大数组。⚠️警告 2ZGC 的 CPU 开销ZGC 虽然停顿短但平时 CPU 占用比 G1 高。大概高 10% 左右。如果机器 CPU 本来就紧缺得慎重。✅推荐 2混合使用锁与无锁能不用锁就不用锁。比如用AtomicInteger代替synchronized计数。无锁并发GC 怎么扫都跟你没关系。六、综合实战演示最后咱们写个完整的示例。结合 AQS 和 ZGC 配置做一个高可用的资源池。import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * 高并发资源池示例 * 结合 AQS 锁与 GC 优化策略 */ public class ResilientResourcePool { private final TimeoutLock lock new TimeoutLock(); private final AtomicInteger availableResources new AtomicInteger(10); private static final int MAX_RETRIES 3; /** * 获取资源带重试机制 * 防止因 GC 停顿导致的单次超时 */ public boolean acquireResource(long timeoutMs) { for (int i 0; i MAX_RETRIES; i) { try { // 尝试获取锁 if (lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) { try { // 检查资源是否充足 if (availableResources.get() 0) { availableResources.decrementAndGet(); return true; } } finally { lock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } // 如果因为 GC 停顿超时稍微等一会儿再试 if (i MAX_RETRIES - 1) { try { Thread.sleep(50); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } return false; } /** * 释放资源 */ public void releaseResource() { // 简单模拟实际生产需加锁保证原子性 availableResources.incrementAndGet(); } public static void main(String[] args) { // 模拟 JVM 启动参数-XX:UseZGC -Xms2g -Xmx2g System.out.println(当前 GC 策略: java.lang.management.ManagementFactory.getGarbageCollectorMXBeans()); ResilientResourcePool pool new ResilientResourcePool(); ExecutorService executor Executors.newCachedThreadPool(); for (int i 0; i 20; i) { executor.submit(() - { if (pool.acquireResource(1000)) { System.out.println(Thread.currentThread().getName() 获取资源成功); pool.releaseResource(); } else { System.out.println(Thread.currentThread().getName() 获取资源失败); } }); } executor.shutdown(); } }这段代码里acquireResource加了重试。哪怕第一次因为 GC 停顿超时第二次还有机会。这就是生产级代码该有的韧性。七、总结AQS 是并发控制的骨架GC 是内存管理的血液。两者在运行时紧密交织。锁竞争会让线程挂起GC 停顿会让挂起的线程无法醒来。想解决卡顿不能只调锁得盯着 GC 日志看。G1 适合大堆、可预测停顿。ZGC 适合超大堆、极低延迟。选对工具配合合理的锁粒度。你的系统才能在并发高压下依然稳如老狗。