多线程框架JUC

📅 2026/8/10 21:59:30
多线程框架JUC
目录1、核心思想CASAQS2、JUC的核心架构锁框架 (Locks Framework)原子类框架 (Atomic Classes Framework)并发集合框架 (Concurrent Collections Framework)线程池与执行框架 (Executor Framework)同步器 (Synchronizers)3、总结JUC是java.util.concurrent包及其子包如java.util.concurrent.atomic和 java.util.concurrent.locks的缩写提供了一整套用于解决并发编程问题的工具和框架。在早期开发中解决多线程并发问题主要通过wait()/notify()机制和synchronized关键字这些技术虽然简单实用但也存在一些问题锁不够灵活不适用复杂的并发业务而且在锁竞争激烈时会升级成重量级锁性能较低等等。JUC工具包对上述问题则有些很好的解决方案。1、核心思想JUC高性能的基础依赖CAS和AQS这两个核心概念。CASCAS(Compare And Swap)是一种在多线程环境下实现同步的无锁化方案。它包含三个操作数内存位置(V)、预期原值(A)、新值(B)当且仅当V的值等于A时才会用B更新V的值否则什不进行更新返回false整个操作是一个CPU级别的原子指令。在Java中通过Unsafe类和 java.util.concurrent.atomic包下的原子类暴露给开发者使用例如AtomicInteger、AtomicLong、AtomicReference 等这些原子类提供了一系列原子操作方法保证操作的原子性和线程安全性。 优点避免了互斥锁的开销。 缺点ABA问题(可通过AtomicStampedReference解决)在竞争激烈的情况下CAS 操作可能会多次自旋尝试导致 CPU 资源浪费只能保证一个共享变量的原子操作。AQSAQS(AbstractQueuedSynchronized)是构建锁和同步器的框架JUC中大多数同步器如ReentrantLock、Semaphore、CountDownLatch......都是基于AQS构建的。 核心思想通过state变量精确管理资源状态结合CLH队列实现线程阻塞与唤醒。当资源空闲时新请求的线程可直接获取资源当资源被占用时请求线程进入队列等待释放资源的线程负责更新状态并唤醒后续等待的线程。 state状态变量一个volatile int成员变量表示同步状态。例如在 ReentrantLock中state0表示锁空闲state1表示锁被占用state1表示锁被同一个线程重入。 CLH等待队列一个FIFO的双向队列用于管理获取资源失败的线程AQS会将线程封装成一个节点Node并入队。 模板方法模式AQS是JUC报下的一个抽象类本身并没实现具体的并发功能只是定义了获取和释放资源的顶层逻辑具体的资源获取/释放规则如独占还是共享则留给子类去实现如tryAcquire, tryRelease。使用者只需继承AQS并实现其获取/释放资源的方法如tryAcquire, tryRelease即可轻松实现一个自定义的同步器。2、JUC的核心架构锁框架 (Locks Framework)提供了比synchronized更灵活的锁操作支持尝试、超时、中断等核心接口是Condition、Lock、ReadWriteLock。核心实现类为ReentrantLock可重入锁和ReentrantReadWriteLock可重入读写锁如图1所示。图1在JDK1.6及以后ReentrantLock与synchronized在性能上差不多因为从JDK1.6对synchronized进行了锁升级引入偏向锁、轻量级锁、锁粗化等技术优化后引入了使得两者的性能差距已经变得非常小。 ReentrantLock对比synchronized的优势在于更细粒度的控制和更强大的功能例如可中断的锁获取在等待锁时可以响应中断超时获取锁尝试获取锁如果失败会在指定时间内重试超时则放弃同时支持公平锁和非公平锁公平锁会按照线程请求的顺序分配锁非公平锁不保证线程获取锁的顺序分组唤醒通过Condition接口一个锁Lock可以绑定多个条件队列Condition实现更精确的线程唤醒等等。 ReentrantReadWriteLock则可以提高读多写少场景的性能实现读读共享读写互斥写写互斥的功能。原子类框架 (Atomic Classes Framework)基于CAS(Compare-And-Swap)无锁算法实现的线程安全类性能远超synchronized如图2。图2public class Counter { private int count 0; public synchronized void increment() { //使用synchronized count; } public synchronized int getCount() { return count; } } public class Counter { private AtomicInteger count new AtomicInteger(0); public void increment() { //使用AtomicInteger的CAS操作 count.incrementAndGet(); } public int getCount() { return count.get(); } }上述使用AtomicInteger.incrementAndGet()更加高效。并发集合框架 (Concurrent Collections Framework)java.util包中的集合类如 ArrayList, HashMap, HashSet等是非线程安全的多线程环境下使用需要用对应的Vector或按照如下形式包装。ListString syncArrayList Collections.synchronizedList(new ArrayList()); MapString, Object syncHashMap Collections.synchronizedMap(new HashMap());线程安全的集合类位于java.util.concurrent包下性能远高于Vector或用Collections.synchronizedXXX()包装的集合。列举几个常见类如下分类核心思想特性实现类分段锁将整个数据段分成多“段Segment每段独立加锁。当多个线程访问不同段的数据时可以真正并行从而显著提高并发吞吐量。锁粒度介于同步集合锁整个对象和无锁之间。比全表锁并发度高因为并发访问不同段的数据不会竞争JDK1.8后已过时ConcurrentHashMap(JDK7)写时复制读操作在原数组一个不变的快照上进行写操作增、删、改并不直接在原集合上进行而是先复制一份底层的数据数组在副本上执行修改修改完成后再将集合的引用指向这个新的副本。读性能高写性能差内存开销大数据弱一致性读操作可能无法立即感知到写操作的最新结果适用于读多写极少的场景CopyOnWriteArrayListCopyOnWriteArraySetCAS无锁化利用硬件原语CAS来实现非阻塞的线程安全操作线程在更新数据时不会被挂起通过不断重试CAS操作直到成功。在高竞争环境下性能远优于传统的synchronized。可能出现“ABA问题”ConcurrentHashMap(JDK8)ConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueueConcurrentLinkedDeque使用时需根据实际场景进行选择。需要高并发读写一个Map 首选ConcurrentHashMap (JDK 1.8)。JDK8及以后摒弃分段锁采用synchronizedCAS红黑树的方式。对桶数组元素的头节点加synchronized锁锁粒度更细。大量使用CAS进行无锁化操作如size计算。链表过长时默认 8转换为红黑树提升查询效率。需要一个线程安全的队列 如果对性能要求极高且能接受无界选择 ConcurrentLinkedQueue。一个无界、非阻塞的线程安全队列。完全使用CAS操作实现是无锁编程的典范。 如果需要有界阻塞功能生产者-消费者模型则应选择 BlockingQueue 接口的实现类如ArrayBlockingQueue, LinkedBlockingQueue它们也属于JUC框架但属于另一类阻塞队列。需要一个线程安全的List且读远大于写 选择CopyOnWriteArrayList。需要一个线程安全且有序的Map 选择ConcurrentSkipListMap并发版本的TreeMap基于跳表实现提供排序功能。需要一个线程安全且有序的Set 选择ConcurrentSkipListSet基于ConcurrentSkipListMap实现的并发有序Set。线程池与执行框架 (Executor Framework)位于java.util.concurrent包中线程池是预先设置一定数量的线程常驻内存避免频繁创建和销毁线程带来的性能开销同时将任务的提交与执行解耦。核心接口 Executor最基础的接口只有一个execute(Runnable command) 方法。 ExecutorService继承了Executor提供更全面的功能。 ScheduledExecutorService: 调度接口继承了ExecutorService支持延迟和周期性任务。 核心实现类 ThreadPoolExecutor 最核心的线程池实现可以通过参数精细控制核心线程数、最大线程数、存活时间、工作队列、拒绝策略等。 ScheduledThreadPoolExecutor: 继承自 ThreadPoolExecutor实现了ScheduledExecutorService接口用于调度任务。 ForkJoinPool: JDK7引入遵循Fork/Join任务分解思想用于高效并行执行任务将大任务拆分成小任务Fork最后合并结果Join。而且每个线程有自己的工作队列当一个线程完成自己队列的任务后可以从其他线程的队列尾部“窃取”任务来执行能高效利用CPU资源特别适合递归分治类任务如归并排序。 工具类 Executors 工厂方法用于快速创建常见的线程池但其内部也是调用ThreadPoolExecutor。生产环境需谨慎使用该工具类因为有些没有设定线程数和队列的上限有资源耗尽内存溢出的风险。 要执行的任务 Runnable接口和Callable接口表示要执行的任务。Callable表示有返回值的任务可以返回结果和抛出异常。返回类型为Future即一个异步计算的结果。可以通过 get() 方法获取结果阻塞cancel() 取消任务isDone() 查询是否完成核心实现类是FutureTask。同步器 (Synchronizers)几个非常实用的高级工具类协调线程之间的控制流。CyclicBarrier (循环栅栏)让一组线程互相等待直到所有线程都到达一个公共屏障点。可以设置一个Runnable任务在所有线程到达后执行屏障在释放等待线程后可以循环使用。适用场景多线程计算数据最后合并计算结果将一个大任务拆分成几个子任务所有子任务完成后再执行一个汇总任务。CountDownLatch (倒计时门闩)让一个或多个线程等待直到其他线程完成一系列操作。初始化一个计数器await()方法阻塞直到countDown()被调用多次后计数器归零。计数器只能使用一次无法重置。与CyclicBarrier不同的是CountDownLatch的计数器是递减的由任务线程控制CyclicBarrier的计数器是递增的由等待线程自身控制且可重复使用。适用场景主线程等待所有子线程初始化完毕后再执行。Semaphore (信号量)控制同时访问特定资源的线程数量流量控制。初始化一个许可证数量acquire() 获取一个许可release() 释放一个许可。适用场景数据库连接池限制、限流。Phaser (阶段器)JDK7加入更强大和灵活的可重复使用的同步屏障可以替代CountDownLatch和CyclicBarrier。支持动态调整参与的线程数支持多阶段Phase的同步。适用场景:一个多阶段的任务每个阶段都需要所有线程完成才能进入下一阶段。Exchanger (交换器)提供一个同步点两个线程可以在该点交换数据。当一个线程调用exchange()方法后它会阻塞等待直到另一个线程也调用此方法然后交换数据。适用场景一个线程生产数据另一个线程消费数据它们通过Exchanger交换缓冲区。3、总结JUC是java中强大且专业的并发处理框架再实际开发中往往使用其中一两种技术便能很好的解决问题上述便是JUC的基本原理、框架、以及常见的一些解决方案。