当前位置: 首页 > news >正文

Spring Boot中的JUC并发解析

文章目录

  • 引言
  • 第一章:JUC核心概念与基石
    • 1.1 Executor框架:任务提交与执行的解耦
    • 1.2 锁与同步器:精细化并发控制
    • 1.3 原子操作类 (Atomic):无锁并发的利器*
  • 第二章:Spring Boot与JUC的无缝集成
    • 2.1 @Async注解:简化异步方法调用
    • 2.2 TaskExecutor与线程池配置:性能调优的关键
  • 第三章:现代异步编程模型:CompletableFuture
    • 3.1 链式调用:构建复杂的异步工作流
    • 3.2 异常处理机制
    • 3.3 结合@Async的最佳实践示例
  • 第四章:高并发计数器实现策略
    • 4.1 AtomicInteger/AtomicLong:基础原子计数器
    • 4.2 LongAdder:高并发性能之王
    • 4.3 ConcurrentHashMap:多维度计数场景
    • 4.4 决策指南
  • 第五章:面向未来的并发:虚拟线程与结构化并发
    • 5.1 虚拟线程 (Virtual Threads):Project Loom的革命
  • 第六章:JUC同步器在Spring Boot服务层的应用场景与示例
    • 6.1 ReentrantLock / ReadWriteLock: 实现服务内缓存
    • 6.2 Semaphore: 实现API调用限流
    • 6.3 CountDownLatch: 并行处理数据并等待汇总

引言

随着现代应用程序对高吞吐量和低延迟需求的日益增长,并发编程已成为后端开发不可或缺的核心技能。Java并发包(java.util.concurrent,简称JUC)提供了强大而丰富的工具集,用以应对复杂的多线程挑战。本文将从JUC的核心组件出发,逐步深入到其在Spring Boot中的集成方式、高级异步编程模型、性能调优最佳实践,并涵盖Java最新的并发特性,如虚拟线程和结构化并发。

第一章:JUC核心概念与基石

在深入探讨与Spring Boot的集成之前,我们必须首先理解JUC提供的基础构建块。这些工具是所有高级并发模式的基石。

1.1 Executor框架:任务提交与执行的解耦

Executor框架是JUC的核心,它将任务的定义(通常是Runnable或Callable)与任务的执行机制完全分离 。这种设计思想极大地提升了代码的可维护性和灵活性。

  • Executor接口:这是最顶层的接口,仅定义了一个execute(Runnable command)方法,用于接收一个任务并安排其执行。
  • ExecutorService接口:继承自Executor,提供了更完整和强大的异步任务执行框架。它支持提交带返回值的任务(Callable),并能通过Future对象管理任务的生命周期(如取消任务、获取结果)和关闭线程池。
  • ThreadPoolExecutor类:这是ExecutorService最核心、最常用的实现类。它是一个功能完备的线程池,允许开发者精细化控制其行为,包括核心线程数、最大线程数、任务队列、线程存活时间、拒绝策略等关键参数。在Spring Boot中自定义线程池,本质上就是配置ThreadPoolExecutor。
  • ScheduledExecutorService接口:扩展了ExecutorService,增加了对延迟执行和周期性执行任务的支持。
  • Executors工具类:提供了一系列静态工厂方法,用于快速创建常见的线程池类型,如newFixedThreadPool(固定大小线程池)和newCachedThreadPool(可缓存线程池)。但在生产环境中,更推荐直接使用ThreadPoolExecutor的构造函数进行自定义配置,以避免潜在的资源耗尽风险

1.2 锁与同步器:精细化并发控制

相比于Java内置的synchronized关键字,JUC提供了功能更强大、更灵活的锁和同步工具。

  • Lock接口:提供了比synchronized更广泛的锁定操作。其核心实现ReentrantLock(可重入锁)支持公平性选择、可中断的锁获取、尝试非阻塞地获取锁以及超时获取锁等高级功能。
  • ReadWriteLock接口:读写锁接口,其实现ReentrantReadWriteLock允许多个线程同时读取共享资源(读锁),但在有线程写入资源时(写锁),其他所有读写操作都必须等待。这种“读共享、写独占”的特性非常适合“读多写少”的业务场景,能显著提升并发性能。
  • Semaphore(信号量)‍:用于控制同时访问特定资源的线程数量。它维护了一组“许可证”,线程必须先获得许可证才能访问资源,使用完毕后释放许可证。这在实现资源限流、数据库连接池管理等场景中非常有用。
  • CountDownLatch(倒数门闩)‍:允许一个或多个线程等待其他一组线程完成操作。它像一个倒计时计数器,一个线程可以调用await()方法阻塞等待,直到其他线程通过调用countDown()方法使计数器归零。它是一次性的,计数器归零后无法重置。
  • CyclicBarrier(循环屏障)‍:让一组线程互相等待,直到所有线程都到达一个共同的屏障点,然后才能继续执行。与CountDownLatch不同,CyclicBarrier是可重用的,在线程释放后可以用于下一轮等待。

1.3 原子操作类 (Atomic):无锁并发的利器*

java.util.concurrent.atomic包提供了一系列原子操作类,如AtomicInteger、AtomicLong和AtomicReference。这些类利用了现代CPU支持的 CAS(Compare-And-Swap,比较并交换)‍ 指令,实现了非阻塞的、线程安全的数据更新操作。

  • 原子性保证:incrementAndGet()等方法是原子性的,可以在没有synchronized或Lock的情况下安全地进行计数等操作,避免了锁带来的上下文切换和调度开销,在高并发场景下通常有更好的性能。
  • 可见性保证:Atomic*类的内部值通常由volatile关键字修饰,这保证了当一个线程修改了原子变量的值后,该变更对其他线程立即可见。

第二章:Spring Boot与JUC的无缝集成

Spring Boot通过其强大的自动化配置和抽象,极大地简化了JUC在项目中的使用。

2.1 @Async注解:简化异步方法调用

Spring框架提供的@Async注解是实现异步编程最便捷的方式。

  1. 启用异步支持:首先,需要在应用的配置类上添加@EnableAsync注解来开启对异步任务处理的支持。
  2. 标记异步方法:然后,在任何需要异步执行的public方法上添加@Async注解。Spring会自动为这个方法创建一个代理,使其调用在一个独立的线程中执行,从而立即返回,不阻塞主调用线程。
  3. 返回值:如果异步方法没有返回值,其签名应为void。如果需要返回结果,方法的返回类型应该是Future< T >或更强大的CompletableFuture< T > 。

2.2 TaskExecutor与线程池配置:性能调优的关键

默认情况下,@Async会使用一个SimpleAsyncTaskExecutor,该执行器每次调用都会创建一个新线程,不会复用,可能导致严重的性能问题 。因此,在生产环境中,必须自定义线程池

Spring Boot为此提供了ThreadPoolTaskExecutor,它是对JUC中ThreadPoolExecutor的封装,更易于在Spring环境中配置。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@Configuration
@EnableAsync
public class AsyncConfig {@Bean("myTaskExecutor") // 定义Bean的名称,方便@Async注解指定public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数:根据CPU核心数和任务类型(CPU密集型/IO密集型)设定// 对于IO密集型任务,可以设置得稍大,如CPU核心数的2倍int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;executor.setCorePoolSize(corePoolSize);// 最大线程数:当任务队列满了之后,可以创建的最大线程数executor.setMaxPoolSize(corePoolSize * 2);// 任务队列容量:用于缓存等待执行的任务executor.setQueueCapacity(500);// 线程空闲时间:超过核心线程数的线程,在空闲多长时间后被销毁 executor.setKeepAliveSeconds(60);// 线程名前缀:方便日志追踪和问题排查executor.setThreadNamePrefix("MyAsync-");// 拒绝策略:当线程池和队列都满时的处理策略 // CallerRunsPolicy:由调用者线程自己执行该任务,这是一种很好的降级策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 初始化线程池executor.initialize();return executor;}
}

使用自定义线程池:
在异步方法上,通过@Async注解的value属性指定要使用的Executor Bean的名称。

@Service
public class MyAsyncService {@Async("myTaskExecutor")public void doSomethingAsync() {// ... 异步执行的逻辑}
}

第三章:现代异步编程模型:CompletableFuture

虽然@Async结合Future很方便,但Future的API功能有限,其get()方法是阻塞的。Java 8引入的CompletableFuture彻底改变了这一局面,它提供了函数式、非阻塞的链式编程模型,是构建复杂异步工作流的首选。

3.1 链式调用:构建复杂的异步工作流

CompletableFuture支持将多个异步任务串联、并联或组合起来,形成一个处理流水线。

  • 创建任务
    • CompletableFuture.runAsync(Runnable, Executor): 运行无返回值的异步任务。
    • CompletableFuture.supplyAsync(Supplier< U >, Executor): 运行有返回值的异步任务。
  • 串行执行
    • thenApply(Function): 当上一个任务完成时,将其结果作为输入,执行一个转换函数,并返回一个新的CompletableFuture。
    • thenAccept(Consumer): 消费上一个任务的结果,无返回值。
    • thenRun(Runnable): 在上一个任务完成后执行一个Runnable。
    • thenCompose(Function): 类似于thenApply,但该函数返回的是一个CompletableFuture。用于连接两个有依赖关系的异步任务,避免CompletableFuture<CompletableFuture< T >>的嵌套结构。
  • 并行组合
    • thenCombine(other, BiFunction): 将两个独立的CompletableFuture的结果合并处理。
    • allOf(cfs…): 等待所有给定的CompletableFuture执行完成。
    • anyOf(cfs…): 当任何一个给定的CompletableFuture完成时即完成。

3.2 异常处理机制

CompletableFuture提供了强大的异常处理能力,可以将异常作为数据流的一部分进行处理。

  • exceptionally(Function): 当异步链中任何一个环节出现异常时,会跳过后续的正常处理逻辑,直接进入exceptionally块进行处理,并可以返回一个默认值或替代结果。
  • whenComplete(BiConsumer): 无论任务是正常完成还是异常结束,都会执行。它可以获取结果和异常,但不能改变返回结果。
  • handle(BiFunction): 类似于whenComplete,但它可以处理结果和异常,并返回一个新的结果,从而改变后续链的走向。

3.3 结合@Async的最佳实践示例

在Spring Boot中,可以将@Async方法与CompletableFuture结合,充分利用Spring的线程池管理和CompletableFuture的链式编程能力。

场景:假设一个订单服务需要异步查询用户信息,然后根据用户信息异步查询用户积分。

线程池配置:复用第二章中的AsyncConfig。

服务层实现

@Service
public class OrderService {@Autowiredprivate UserService userService;@Autowiredprivate CreditService creditService;// 主调用方法public CompletableFuture<String> getOrderDetails(String userId) {// 1. 异步获取用户信息,并指定使用自定义线程池return userService.getUserById(userId)// 2. 链式调用:获取到用户信息后,异步获取用户积分.thenCompose(user -> creditService.getCreditByUser(user))// 3. 链式调用:获取到积分后,组合成最终的字符串结果.thenApply(credit -> "用户信息和积分为: " + credit)// 4. 异常处理:在整个链条的任意环节发生异常时,提供一个降级结果.exceptionally(ex -> {log.error("获取订单详情失败: {}", ex.getMessage());return "获取订单详情失败,请稍后重试";});}
}@Service
class UserService {@Async("myTaskExecutor") // 使用自定义线程池public CompletableFuture<String> getUserById(String userId) {try {// 模拟耗时IO操作Thread.sleep(1000);if ("error".equals(userId)) {throw new RuntimeException("用户不存在");}return CompletableFuture.completedFuture("用户" + userId);} catch (InterruptedException e) {return CompletableFuture.failedFuture(e);}}
}@Service
class CreditService {@Async("myTaskExecutor") // 使用自定义线程池public CompletableFuture<String> getCreditByUser(String user) {try {// 模拟耗时IO操作Thread.sleep(1000);return CompletableFuture.completedFuture(user + "有100积分");} catch (InterruptedException e) {return CompletableFuture.failedFuture(e);}}
}

这个例子展示了如何通过@Async将方法包装成返回CompletableFuture的异步任务,并利用thenCompose、thenApply和exceptionally构建了一个健壮、清晰的异步处理流程。异常会在链中传播,直到被exceptionally捕获。

第四章:高并发计数器实现策略

在秒杀、限流、实时统计等场景中,高并发计数器是一个常见需求。JUC为此提供了多种实现方案,选择哪一种取决于具体的并发级别和业务需求。

4.1 AtomicInteger/AtomicLong:基础原子计数器

机制:基于CAS无锁操作,在低到中等并发下性能优异,因为它避免了锁的开销。
可见性与原子性:通过volatile和CAS保证了单次操作(如incrementAndGet)的原子性和多线程间的可见性。
瓶颈:在高并发竞争下,大量线程对同一个原子变量进行CAS操作,只有一个能成功,其他线程会不断自旋重试,这会消耗大量CPU资源,导致性能下降。
适用场景:并发竞争不激烈或需要强一致性实时值的场景,如生成全局唯一序列号。

4.2 LongAdder:高并发性能之王

  • 机制:Java 8引入,专为高并发统计场景设计。其核心思想是“空间换时间”,内部维护一个Cell数组(分段)。当发生并发更新时,线程会根据哈希被分散到不同的Cell上进行CAS更新,而不是所有线程都竞争同一个变量。获取总数时,再将所有Cell的值和base值累加。
  • 性能:通过分散热点,极大地减少了CAS冲突,因此在高并发下吞吐量远超AtomicLong。
  • 可见性与原子性:同样基于CAS保证单点更新的原子性。但sum()方法获取的是一个“瞬时非精确值”,在获取总和的过程中,其他线程可能仍在修改Cell,所以它提供的是最终一致性,而非强一致性。
  • 适用场景:高并发的统计和监控场景,如API调用次数、网站PV/UV统计,这些场景对数据的实时精确性要求不高,但对吞吐量要求极高。

4.3 ConcurrentHashMap:多维度计数场景

虽然ConcurrentHashMap本身是一个线程安全的哈希表,但它也可以用来实现功能更复杂的计数器。

  • 机制:通过分段锁(Java 7)或CAS+synchronized(Java 8+)保证线程安全。
  • 适用场景:当你需要对多个不同的key进行计数时,ConcurrentHashMap是自然的选择。例如,统计每个URL的访问次数。这里结合LongAdder可以获得极佳的并发性能。
ConcurrentHashMap<String, LongAdder> counters = new ConcurrentHashMap<>();
// 对某个URL的访问计数加一
counters.computeIfAbsent("url/path", key -> new LongAdder()).increment();

4.4 决策指南

特性/工具AtomicLongLongAdderConcurrentHashMap<String, LongAdder>
并发级别低-中
核心机制单点CAS分段CAS哈希分桶 + 分段CAS
性能高并发下因自旋而下降高并发下持续高吞吐适用于多Key场景,单Key性能同LongAdder
一致性强一致性最终一致性最终一致性
适用场景序列号生成、需要精确实时值的计数流量统计、监控指标等不要求强一致性的汇总按维度统计,如统计各商品销量、各接口调用量

第五章:面向未来的并发:虚拟线程与结构化并发

随着Java版本的演进,JUC也在不断进化。自JDK 19起引入并于JDK 21正式发布的Project Loom项目,为Java并发编程带来了革命性的变化。

5.1 虚拟线程 (Virtual Threads):Project Loom的革命

  • 核心理念:虚拟线程是由JVM管理的一种极其轻量级的线程,它不直接映射到操作系统(OS)的平台线程。成千上万甚至上百万个虚拟线程可以运行在少数几个平台线程之上(M:N调度)。创建和切换虚拟线程的成本极低。

  • 适用场景:非常适合I/O密集型任务,即那些大部分时间都在等待网络响应或磁盘读写的任务。当一个虚拟线程执行I/O阻塞操作时,JVM会自动将其“挂起”,并让底层的平台线程去执行另一个虚拟线程,从而极大地提高了硬件资源的利用率和应用吞吐量。对于CPU密集型任务,虚拟线程没有优势。

  • 在Spring Boot中启用与使用 (要求 Java 21+ 和 Spring Boot 3.2+):
    1. 在application.properties中启用:

    spring.threads.virtual.enabled=true

    启用后,Spring Boot将自动配置Tomcat等Web服务器使用虚拟线程来处理每个HTTP请求,并为@Async配置一个基于虚拟线程的执行器。
    2. 自定义虚拟线程执行器:

    @Beanpublic Executor virtualThreadExecutor() {// JDK 21 提供了便捷的工厂方法return Executors.newVirtualThreadPerTaskExecutor();}
    
  • 性能对比分析:
    在处理大量并发I/O请求的场景下(如微服务调用),使用虚拟线程相比传统的ThreadPoolExecutor(平台线程池)有显著的性能优势。

  • 吞吐量 (Throughput) :显著提高,因为少量平台线程可以服务大量并发请求。

  • 延迟 (Latency) :通常更低且更稳定,因为线程创建和阻塞的开销几乎可以忽略不计。

  • 内存消耗 (Memory Usage) :大幅降低,每个虚拟线程只占用几百字节的内存,而平台线程通常需要1-2MB。

第六章:JUC同步器在Spring Boot服务层的应用场景与示例

6.1 ReentrantLock / ReadWriteLock: 实现服务内缓存

业务场景:某个服务需要频繁读取一些不经常变化的配置数据(如城市列表)。为了避免每次都查询数据库,我们在服务内部实现一个简单的内存缓存,并使用ReadWriteLock保证线程安全,提升读取性能。

@Service
public class CityCacheService {private final Map<String, List<String>> cache = new HashMap<>();private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();// 读操作:使用读锁,允许多个线程同时读取public List<String> getCitiesByProvince(String province) {readLock.lock();try {return cache.get(province);} finally {readLock.unlock();}}// 写操作:使用写锁,独占访问public void refreshCache() {writeLock.lock();try {// 模拟从数据库加载数据Map<String, List<String>> newData = loadFromDatabase();cache.clear();cache.putAll(newData);} finally {writeLock.unlock();}}private Map<String, List<String>> loadFromDatabase() {// ... 数据库查询逻辑return new HashMap<>();}
}

6.2 Semaphore: 实现API调用限流

业务场景:服务需要调用一个昂贵的第三方API,且对方限制了并发调用次数(例如,最多5个并发)。我们可以使用Semaphore来控制对该API客户端的并发访问。

@Service
public class ThirdPartyApiService {// 创建一个信号量,许可证数量为5 private final Semaphore semaphore = new Semaphore(5);public String callApi(String query) throws InterruptedException {// 尝试获取一个许可证,如果获取不到,线程会阻塞 semaphore.acquire();try {// --- 获得了许可证,可以执行调用 ---System.out.println(Thread.currentThread().getName() + " 正在调用API...");// 模拟API调用耗时Thread.sleep(2000);return "API result for " + query;} finally {// 无论成功还是失败,都必须释放许可证semaphore.release();System.out.println(Thread.currentThread().getName() + " 已释放许可。");}}
}

6.3 CountDownLatch: 并行处理数据并等待汇总

业务场景:一个数据处理任务需要从3个不同的数据源(如三个不同的文件或API)并行拉取数据,然后在所有数据都拉取完成后进行合并处理。

@Service
public class DataProcessingService {@Autowiredprivate Executor taskExecutor; // 注入自定义的线程池public void processData() throws InterruptedException {// 初始化一个计数为3的门闩CountDownLatch latch = new CountDownLatch(3);List<String> results = Collections.synchronizedList(new ArrayList<>());taskExecutor.execute(() -> {results.add(fetchFromSourceA());latch.countDown(); // 完成一个任务,计数器减一});taskExecutor.execute(() -> {results.add(fetchFromSourceB());latch.countDown();});taskExecutor.execute(() -> {results.add(fetchFromSourceC());latch.countDown();});// 阻塞当前线程,直到latch的计数变为0latch.await();// --- 所有数据源都已拉取完成,可以进行汇总处理 ---System.out.println("数据全部拉取完成,开始合并处理...");System.out.println(results);}private String fetchFromSourceA() { /* ... */ return "DataA"; }private String fetchFromSourceB() { /* ... */ return "DataB"; }private String fetchFromSourceC() { /* ... */ return "DataC"; }
}
http://www.dtcms.com/a/545261.html

相关文章:

  • k8s一站式学习
  • 7.1.4 大数据方法论与实践指南-数据服务接口
  • 网安面试题收集(6)
  • 建设网站需要多少钱济南兴田德润o地址济南网站建设加q479185700
  • 站长之家appwordpress添加版权
  • LeetCode每日一题——Pow(x, n)
  • 6.3.2.2 大数据方法论与实践指南-离线任务质量治理
  • 成都php网站制作程序员网站建设公司新报价
  • SODA v9.5.2 甜盐相机,自然美颜相机
  • 【小白笔记】判断一个正整数是否为质数(Prime Number)-循环语句中的else语句
  • 传奇网站一般怎么做的在国外做h网站怎么样
  • Next.js, Node.js, JavaScript, TypeScript 的关系
  • 做一个综合商城网站多少钱合肥seo关键词排名
  • 网站开发与管理对应的职业及岗位优质的seo网站排名优化软件
  • 新人如何学会安装与切换Rust版本:从工具链管理到生产实践
  • 公司网站制作源码wordpress 最快的版本
  • Rust:与JSON、TOML等格式的集成
  • 应用商城发布项目
  • 6.3.3.1 大数据方法论与实践指南-大数据质量度量指标体系
  • 二叉树----规矩森严的“家族树”(第11讲)
  • 随州网站建设有哪些南昌网站建设是什么意思
  • php免费企业网站模板祥云县住房和城乡建设网站
  • 宏观经济走势对网民互联网消费行为的影响:基于开源链动2+1模式AI智能名片S2B2C商城小程序的实证分析
  • 网站开发 环境品牌设计概念
  • 网站建设加盟培训网站内图片变换怎么做
  • Linux设置服务开机自启动脚本
  • wordpress适合做大型网站吗潍坊专业人员继续教育
  • openpnp - 如果出现不正常的情况,需要将设备和主板重新上电
  • 【音视频】WebRTC连接建立流程详解
  • 从零开始的C++学习生活 17:异常和智能指针