引言
在Java并发编程领域,java.util.concurrent(简称JUC)包是一座恢宏的建筑,其中的ReentrantLock、Semaphore、CountDownLatch等高级同步工具如同璀璨的明珠,而AbstractQueuedSynchronizer(AQS)则是支撑它们的坚固地基。然而,在AQS之下,还隐藏着一个更为底层、更为基础的构建块------java.util.concurrent.locks.LockSupport。
如果说AQS是并发世界的"操作系统",那么LockSupport就是其内核中的"系统调用"------它直接与JVM和操作系统的线程调度器交互,提供了最原始、最高效的线程阻塞与唤醒能力。本文将以此为切入点,系统梳理JUC包的核心知识体系。
一、Java并发包全景图
JUC包是Java 5.0引入的并发编程工具库,其核心组件可划分为以下几大类:
| 模块 | 核心功能 | 代表类 |
|---|---|---|
| 原子类(Atomic) | 无锁的线程安全操作 | AtomicInteger、AtomicReference、LongAdder |
| 锁机制(Locks) | 显式锁与条件变量 | ReentrantLock、ReentrantReadWriteLock、Condition |
| 同步器(Tools) | 线程协调与同步 | CountDownLatch、Semaphore、CyclicBarrier、Phaser |
| 并发集合(Collections) | 线程安全的容器 | ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList |
| 执行框架(Executor) | 线程池与任务执行 | ThreadPoolExecutor、Executors、Future、CompletableFuture |
| 阻塞队列(Queues) | 生产者-消费者模式 | ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue |
| 底层支撑 | 原语与框架 | LockSupport、Unsafe、AbstractQueuedSynchronizer(AQS) |
整个JUC体系建立在两大基石之上:Unsafe与CAS ,以及LockSupport。本文将重点聚焦于这些底层构建块。
二、构建块一:Unsafe与CAS
Unsafe是JUC包中最神秘的类之一,它绕过了Java的内存管理机制,提供了直接操作内存和对象字段的能力。其中最重要的功能就是CAS(Compare And Swap,比较并交换)操作。
CAS是一种无锁的原子操作------它先比较某个内存地址的当前值是否等于预期值,如果相等,则将其更新为新值,整个过程以原子方式完成。JUC包内部大量应用了CAS机制作为基础实现并发组件,无锁策略也是采用CAS技术来保证线程执行的安全性。
原子类(AtomicInteger、AtomicLong等)正是基于CAS构建的。当多个线程同时更新一个原子变量时,只有一个线程的CAS会成功,其他线程失败后则进入循环重试(自旋),从而实现了无锁的线程安全。
三、构建块二:LockSupport------线程阻塞的终极原语
LockSupport是JUC底层最基础的构建块,其重要性怎么强调都不为过。
3.1 "许可"模型
LockSupport的核心机制围绕一个简单的许可(permit)模型展开:
-
单次许可 :每个线程最多关联一个许可,许可不会累积,这一点与
Semaphore截然不同。 -
park():如果许可可用,则立即消耗并返回;否则,线程可能进入阻塞状态。 -
unpark(Thread):使目标线程的许可变为可用。如果该线程正在park()中,则会立即被唤醒;如果尚未park(),则下一次park()调用将立即返回。
3.2 解决suspend/resume的致命缺陷
Thread.suspend()和Thread.resume()已被废弃,原因在于它们存在严重的竞态条件问题。例如,如果resume()在suspend()之前被调用,resume()的效果会完全丢失,导致线程永久挂起。而LockSupport的"许可"模型完美地解决了这个问题------unpark()可以在park()之前或之后调用,都能保证线程最终能继续执行,从而保证了活性(liveness)。
3.3 虚假唤醒与正确使用范式
LockSupport.park()可能会"无理由"地返回,这种现象称为虚假唤醒。因此,正确的使用方式必须是在一个循环中反复检查被等待的条件:
java
while (!canProceed()) {
LockSupport.park(this);
}
这种模式确保了即使发生虚假唤醒,线程也会重新检查条件,避免了逻辑错误。
3.4 设计哲学
LockSupport的设计处处体现着经典的设计原则:
-
单一职责原则:职责极其单一------只提供最基础的线程阻塞与唤醒原语,不关心同步逻辑、不维护队列、不处理中断策略。
-
最小惊讶原则 :API设计简单直观,行为可预测,避免了
suspend/resume那样的陷阱。 -
原型模式 :
LockSupport本身就是一个"并发原语",是构建更复杂同步结构的最基本单元,类似于操作系统中的系统调用。
四、构建块三:AQS------并发框架的基石
如果说LockSupport是系统调用,那么AbstractQueuedSynchronizer(AQS)就是整个JUC包的操作系统内核。
4.1 AQS的核心原理
AQS的核心思想是:用一个volatile修饰的int类型变量state表示同步状态,以及一个CLH队列(FIFO双向链表)来管理等待线程:
-
state :同步状态,通过CAS进行原子更新。例如在
ReentrantLock中,state表示锁的重入次数。 -
Node:将每条请求共享资源的线程封装成一个结点,加入CLH队列。
-
模板方法 :AQS定义了一套模板方法(如
acquire()、release()),子类只需实现tryAcquire()、tryRelease()等方法即可完成自定义同步器的构建。
4.2 AQS与LockSupport的协作
在AQS的acquireQueued方法中,当线程需要等待时,最终会调用LockSupport.park(this);当锁被释放时,release方法会调用LockSupport.unpark()来唤醒队列中的下一个线程。正是LockSupport提供了AQS实现高效FIFO队列等待机制所需的最底层能力。
基于AQS构建的同步组件包括ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等。
4.3 ReentrantLock原理速览
以ReentrantLock为例,它是对AQS的一次典型应用:
-
ReentrantLock实现了Lock接口,内部包含三个类:抽象的Sync(继承AQS)、FairSync(公平锁实现)、NonfairSync(非公平锁实现)。 -
线程调用
lock()时,会通过CAS尝试将state从0改为1。成功则获取锁;失败则进入AQS的等待队列,通过LockSupport.park()阻塞自己。 -
支持重入:当持有锁的线程再次调用
lock()时,state递增,退出时递减。
五、JUC核心组件概览
在LockSupport和AQS这两大基石之上,JUC包构建了丰富的高层工具:
5.1 原子类(Atomic)
基于Unsafe+CAS实现,提供无锁的原子操作。核心类包括AtomicInteger、AtomicLong、AtomicReference,以及高性能的LongAdder(适用于高并发计数场景)。
5.2 同步器(Synchronizers)
-
CountDownLatch:允许一个或多个线程等待其他线程完成操作。计数器不可重置,一次性的。
-
Semaphore :信号量,控制同时访问某个资源的线程数量。其"许可"模型与
LockSupport一脉相承。 -
CyclicBarrier:可循环使用的屏障,等待一组线程到达屏障点时一起执行。
-
Phaser:更灵活的同步屏障,支持动态增减参与方。
5.3 显式锁(Locks)
-
ReentrantLock:独占锁,支持重入和公平性选择。
-
ReentrantReadWriteLock:读写锁,读读并发、读写互斥,适用于读多写少场景。
-
Condition :与
ReentrantLock配合使用,实现线程间的等待/通知机制。
5.4 线程池(Executors)
ThreadPoolExecutor是线程池的核心实现,关键参数包括:
-
corePoolSize:核心线程数 -
maximumPoolSize:最大线程数 -
keepAliveTime:空闲线程存活时间 -
workQueue:任务队列(有界或无界) -
handler:拒绝策略
常见的预定义线程池类型有:
-
FixedThreadPool:固定大小,适用于负载稳定的场景。
-
CachedThreadPool:弹性伸缩,适用于大量短生命周期任务。
-
SingleThreadExecutor:单线程化,适用于任务顺序执行的场景。
5.5 并发集合
-
ConcurrentHashMap:高并发的哈希表,JDK 8后采用CAS+synchronized(锁分段取代锁分段技术)。
-
ConcurrentLinkedQueue:基于CAS的无锁队列,性能优秀。
-
CopyOnWriteArrayList:读多写少场景下的容器,写操作复制数组。
-
BlockingQueue 系列:
ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue等,天然支持生产者-消费者模式。
5.6 异步任务编排
CompletableFuture是Java 8引入的异步编程利器,支持任务的链式组合(如thenApply、thenCombine)、异步回调以及任务的批量聚合(allOf、anyOf),内部广泛使用ForkJoinPool和LockSupport实现任务阻塞与唤醒。
六、从单机到云原生的演进
LockSupport的设计思想超越了单机环境,在云原生架构中找到了令人惊喜的映射:
消息队列与事件驱动 :在Kafka或RabbitMQ中,服务A发送一条消息(相当于unpark),服务B在监听队列(相当于park)。消息到达时,消费者被"唤醒"并处理消息。消息就是分布式环境下的"许可"。
分布式信号量与配额系统 :API Gateway、数据库连接池等云服务实现分布式信号量来控制资源并发访问。一个请求尝试获取令牌,配额满时被阻塞,资源释放后等待的请求被授予令牌------这与LockSupport的许可模型在概念上完全一致。
Kube.netes的就绪探针 :Pod启动期间处于"未就绪"状态(park);应用完成初始化并通过就绪探针检查后,Kubernetes将其标记为"就绪"(unpark),并开始向其发送流量。
正如文章所言,"Doug Lea在这个微小类中所展现的对并发本质的洞察,如何跨越时空,持续启发着分布式系统的设计"。
七、总结
回顾整个JUC包的知识体系,我们可以清晰地看到一条"底层→高层"的演进脉络:
text
Unsafe/CAS → LockSupport → AQS → ReentrantLock / Semaphore / CountDownLatch / ...
(原子原语) (阻塞原语) (框架) (面向用户的同步工具)
-
Unsafe与CAS提供了无锁的原子更新能力;
-
LockSupport提供了可靠的线程阻塞与唤醒原语;
-
AQS 利用CAS管理
state,利用CLH队列管理等待线程,利用LockSupport实现阻塞/唤醒,构成了一个完整的同步器框架; -
在AQS之上,JUC包构建了
ReentrantLock、Semaphore、CountDownLatch等面向开发者的丰富工具。
理解这条脉络,不仅有助于掌握JUC包的使用,更能从源码层面理解其设计思想与实现原理。更重要的是,LockSupport所体现的"简单信号协调"这一底层设计哲学,在今天依然指导着云原生、分布式系统等更广阔领域的架构实践。