java.util.concurrent包是Java并发编程的宝藏,它提供了一套经过精心设计的高性能并发工具,涵盖了锁、队列、同步器、原子变量、线程池等各个方面。这个包的诞生极大地降低了并发编程的难度,使开发者无需重复造轮子,就能编写出正确且高效的并发程序。
参考:https://amwtm.cn/category/entrance.html
锁框架是java.util.concurrent.locks包的核心。与synchronized相比,Lock接口提供了更灵活的锁操作:tryLock(非阻塞获取锁)、lockInterruptibly(可中断的获取)、以及tryLock(long time, TimeUnit unit)(超时获取)。ReentrantLock是Lock的主要实现,它支持可重入(同一个线程可以多次获取锁)和公平性选择(公平锁按FIFO顺序分配,非公平锁允许插队)。在大多数场景下,非公平锁的吞吐量高于公平锁。
读写锁ReentrantReadWriteLock允许多个读线程同时访问,但写线程独占访问。这种锁在读多写少的场景下可以显著提高并发性。ReadWriteLock的升级/降级需要谨慎处理——从读锁升级到写锁可能导致死锁。StampedLock(Java 8)是读写锁的改进版本,支持乐观读锁(无阻塞的读取),进一步提高了并发性。StampedLock不是可重入的,使用起来更复杂,但性能更好。
条件变量Condition与锁配合使用,替代传统的wait/notify。每个Condition绑定一个Lock,提供了更精细的等待/通知机制。与wait/notify不同,Condition支持多组等待队列、可中断等待、以及超时等待。典型的用法是:lock.lock(); try { while (condition) { cond.await(); } } finally { lock.unlock(); }。
同步器框架是AbstractQueuedSynchronizer(AQS),它是java.util.concurrent包中许多工具的基础,包括ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等。AQS使用FIFO队列管理等待线程,通过state字段表示同步状态。子类只需要实现tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等方法,AQS负责线程的排队、阻塞和唤醒。
参考:https://amwtm.cn/category/balcony.html
Semaphore(信号量)用于控制同时访问某个资源的线程数量。Semaphore维护一组许可,线程在访问资源前必须获取许可,使用后释放许可。Semaphore可以用于限流(控制并发请求数)、资源池(如数据库连接池)以及实现互斥锁(许可数为1)。Semaphore支持公平和非公平模式。
CountDownLatch是一个倒计数器,允许一个或多个线程等待其他线程完成操作。计数器初始化为N,每个完成操作的线程调用countDown()使计数器减1,等待的线程调用await()阻塞直到计数器归零。CountDownLatch不可重用,一次性的特性使其适用于启动门(等待所有准备工作完成)和结束门(等待所有任务完成)。
CyclicBarrier类似于CountDownLatch,但可重用。它允许多个线程相互等待,直到所有线程都到达屏障点。CyclicBarrier可以传入一个屏障动作(Runnable),当所有线程到达时执行。CyclicBarrier适用于分阶段并行计算——如MapReduce的shuffle阶段。与CountDownLatch不同,CyclicBarrier的计数器可以重置并重用。
Exchanger是一个用于两个线程交换数据的同步点。线程A调用exchange(dataA)阻塞,直到线程B也调用exchange(dataB),然后两个线程交换数据并继续。Exchanger在生产者-消费者模式中很有用,例如两个线程交换缓冲区。
Phaser是Java 7引入的灵活同步器,可以看作是CyclicBarrier和CountDownLatch的升级版。Phaser支持动态注册/注销参与方、多阶段同步、以及分级(父子Phaser)。Phaser的API比前两者复杂,适用于分阶段的任务,如并行模拟或迭代算法。
参考:https://amwtm.cn/category/bathroom.html
并发集合是java.util.concurrent包的重要组成部分。ConcurrentHashMap是线程安全的哈希表,使用分段锁(Java 7)或CAS + synchronized(Java 8+)实现高并发。ConcurrentHashMap的迭代器是弱一致性的,不抛出ConcurrentModificationException。ConcurrentSkipListMap和ConcurrentSkipListSet提供了基于跳表的并发有序映射和集合。CopyOnWriteArrayList和CopyOnWriteArraySet适用于读多写少的场景,写操作复制整个底层数组,成本高昂。
阻塞队列是生产者-消费者模式的核心。ArrayBlockingQueue是有界队列,基于循环数组实现;LinkedBlockingQueue是可选的边界队列,基于链表实现;PriorityBlockingQueue是无界优先级队列;DelayQueue是延迟队列,元素只有在其延迟过期后才能被取出;SynchronousQueue是无缓冲队列,每个插入操作必须等待对应的移除操作。LinkedTransferQueue(Java 7)是LinkedBlockingQueue的增强版,增加了transfer和tryTransfer方法。
原子变量java.util.concurrent.atomic包提供了无锁的线程安全变量。AtomicInteger、AtomicLong、AtomicBoolean和AtomicReference使用CAS(Compare-And-Swap)操作实现原子更新。AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray扩展到了数组。LongAdder和DoubleAdder(Java 8)在高竞争场景下优于AtomicLong,因为它们使用分段累加(类似于ConcurrentHashMap的思路)。LongAccumulator和DoubleAccumulator提供了更灵活的累加函数。
CAS操作是原子变量的底层基础。CAS接受三个参数:内存地址、期望值、新值。如果内存地址的值等于期望值,则更新为新值;否则不做任何操作。CAS返回是否成功。CAS是无锁编程的核心,避免了上下文切换和锁竞争的开销。但CAS也有问题:ABA问题(值从A改为B又改回A,CAS误认为没有变化),通过版本号或AtomicStampedReference可以解决;自旋CAS如果长期不成功,会浪费CPU时间。
线程池ThreadPoolExecutor是Java并发工具包中使用最广泛的组件之一。核心参数包括:核心线程数、最大线程数、空闲线程存活时间、阻塞队列、以及拒绝策略。理解线程池的工作机制非常重要:当线程数小于核心线程数时,创建新线程处理任务;当线程数达到核心线程数时,任务进入队列;当队列满时,创建新线程直到最大线程数;当队列满且线程数达到最大时,执行拒绝策略。
预定义线程池Executors提供了几种常用配置:newFixedThreadPool(固定大小线程池,使用无界队列)、newCachedThreadPool(缓存线程池,核心线程数为0,最大线程数无界,使用同步队列)、newSingleThreadExecutor(单线程池)、newScheduledThreadPool(支持定时和延迟执行)。但阿里巴巴Java开发手册禁止使用Executors创建线程池,因为无界队列可能导致OOM,建议直接使用ThreadPoolExecutor。
ForkJoinPool是Java 7引入的专门用于分治任务的线程池。ForkJoinPool使用工作窃取算法——空闲线程可以从其他线程的队列尾部窃取任务,保持CPU忙碌。RecursiveTask(有返回值)和RecursiveAction(无返回值)是分治任务的基类。ForkJoinPool是并行流(Java 8)的底层实现。CompletableFuture(Java 8)使用ForkJoinPool.commonPool()作为默认的异步执行器。
CompletableFuture是Java 8中最强大的异步编程工具。它支持回调链、组合多个异步任务、异常处理、以及超时控制。CompletableFuture实现了Future和CompletionStage接口,提供了thenApply、thenAccept、thenCompose、thenCombine等组合方法。CompletableFuture的异步编排能力使其成为Java中异步编程的事实标准,与反应式编程(RxJava、Project Reactor)形成竞争。
java.util.concurrent包的演进反映了Java对并发编程的理解不断深化。从最初的synchronized和volatile,到JUC包的锁和原子变量,再到ForkJoinPool和CompletableFuture,Java提供了从低级到高级的全方位并发工具。对于大多数应用,使用高级工具(如CompletableFuture和并发集合)就足够了,只有在性能关键的场景才需要深入到底层工具。理解这些工具的设计原理和使用场景,是成为Java并发专家的必经之路。
参考:https://amwtm.cn