JDK Synchronizers 设计及工作原理与教程

📅 2026/7/7 9:29:00
JDK Synchronizers 设计及工作原理与教程
针对 JDK 同步器Synchronizers从设计思想、核心工作原理、分类与使用教程三个维度为你构建一份系统性的技术指南。一、 总体设计思想AQS 框架JDK 中几乎所有的同步器ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等底层都基于AbstractQueuedSynchronizerAQS构建。理解 AQS 就等于理解了 JDK 同步器的半壁江山。AQS 核心三要素状态变量state一个volatile int state用于表示共享资源的状态。例如ReentrantLockstate0表示未锁定0表示重入次数。Semaphorestate表示剩余许可数。CLH 队列双向链表一个先进先出FIFO的等待队列用于存放争抢资源失败而被阻塞的线程。两种资源共享模式独占模式Exclusive同一时刻只有一个线程能持有资源如ReentrantLock。共享模式Shared多个线程可同时持有资源如Semaphore、CountDownLatch。AQS 设计精髓模板方法模式AQS 不直接实现同步逻辑而是定义了获取/释放资源的骨架流程如acquire()、release()并将具体如何检查状态、修改状态留给子类实现tryAcquire、tryRelease。二、 核心工作原理以 ReentrantLock 为例我们以非公平锁的lock()和unlock()为例拆解 AQS 的运行机制加锁流程lock线程调用lock()AQS 调用子类的tryAcquire()。检查state若state 0通过 CAS 将state置为 1并设置当前线程为独占所有者 →加锁成功。若state 0且当前线程就是所有者state重入→加锁成功。否则 →加锁失败将该线程封装成 Node 节点加入 CLH 队列尾部。入队后的线程执行park()阻塞等待前驱节点唤醒。解锁流程unlock调用unlock()调用子类的tryRelease()将state减 1。若state减为 0清空独占所有者。唤醒 CLH 队列中的下一个有效节点unpark()。被唤醒的线程重新尝试tryAcquire()。关键优化非公平锁在尝试tryAcquire前会直接先进行一次 CAS 抢锁插队提升吞吐量但可能导致饥饿。三、 常用同步器分类与工作机制速查表同步器模式核心作用状态state含义ReentrantLock独占可重入互斥锁支持公平/非公平0未锁n重入次数Semaphore共享信号量控制并发线程数剩余许可证数量CountDownLatch共享门闩等待多个任务完成剩余未完成的计数CyclicBarrier独占内部用 ReentrantLock循环栅栏等待所有线程到达屏障点基于Generation进行重置不同于 AQS stateReentrantReadWriteLock混合读写分离读共享、写独占高16位读锁低16位写锁四、 使用教程与最佳实践含代码示例1. ReentrantLock替代 synchronized// 推荐用法务必在 finally 中释放 private final ReentrantLock lock new ReentrantLock(); public void doSomething() { lock.lock(); try { // 业务逻辑支持 lockInterruptibly、tryLock 等高级特性 } finally { lock.unlock(); } }2. Semaphore限流 / 连接池控制// 模拟允许最多 3 个并发请求 Semaphore semaphore new Semaphore(3); for (int i 0; i 10; i) { new Thread(() - { try { semaphore.acquire(); // 获取许可阻塞直到有可用 System.out.println(Thread.currentThread().getName() 执行); Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // 释放许可 } }).start(); }3. CountDownLatch主线程等待子任务完成CountDownLatch latch new CountDownLatch(3); // 启动 3 个子线程 for (int i 0; i 3; i) { new Thread(() - { // 执行任务... latch.countDown(); // 完成一个计数减 1 }).start(); } latch.await(); // 主线程阻塞直到计数为 0 System.out.println(所有任务完成);4. CyclicBarrier多线程互相等待一起进行下一轮// 允许 3 个线程互相等待全部到达后一起执行 barrierAction CyclicBarrier barrier new CyclicBarrier(3, () - System.out.println(全员到达继续下一轮)); for (int i 0; i 3; i) { new Thread(() - { try { System.out.println(Thread.currentThread().getName() 到达); barrier.await(); // 等待其他线程 System.out.println(Thread.currentThread().getName() 继续执行); } catch (Exception ignored) {} }).start(); }五、 高级避坑指南重要中断响应使用lockInterruptibly()而非lock()以便在等待锁时响应中断。避免await()在循环外使用Condition的await()必须放在while (条件不满足)循环中防止虚假唤醒。CountDownLatch 不可重用计数减到 0 后无法重置。如需重用请用CyclicBarrier。读写锁的锁降级写锁可以降级为读锁先获取写锁再获取读锁再释放写锁但读锁不能升级为写锁。性能考量非公平锁吞吐量通常更高除非强需求避免使用公平锁。六、 学习路径建议初级熟练使用ReentrantLockCondition实现等待/通知掌握Semaphore做限流。中级精读 AQS 源码中的acquireQueued()和shouldParkAfterFailedAcquire()理解节点状态流转SIGNAL、CANCELLED。高级尝试基于 AQS 自定义一个互斥锁只需实现tryAcquire和tryRelease这能让你彻底理解模板方法模式。