在并发编程领域,多线程是提升系统吞吐量与响应速度的核心技术。Java 作为主流后端开发语言,提供了原生的多线程支持;而 Spring 框架在此基础上进行封装优化,让多线程开发更简洁、可控。文章会先从 Java 多线程的基础实现方式和核心概念入手,搭建好知识框架,再过渡到 Spring 对多线程的封装与扩展,展现两者的关联与 Spring 多线程的优势。
Java多线程
基础实现:Thread 类与 Runnable 接口
Java 最初提供了两种最基础的线程创建方式,二者均通过重写 “任务逻辑方法” 实现多线程。
继承Thread类
Thread 类是 Java 线程的核心抽象,继承后需重写run()方法定义任务逻辑,通过start()方法启动线程。
package org.example;
import java.util.ArrayList;
import java.util.List;
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Java Thread执行:" + Thread.currentThread().getName());
}
}
public class App
{
public static void main( String[] args )
{
List<Thread> threads = new ArrayList<>();
// 启动10个线程
for (int i = 0; i < 10; i++) {
threads.add(new MyThread());
}
// 线程,启动!
for (Thread thread : threads) {
thread.start();
}
}
}
实现Runnable接口
Runnable 接口仅定义run()方法,专注于 “任务逻辑”,通过将其传入 Thread 类实例实现线程关联。这种方式规避了 Java 单继承的限制,是更推荐的基础用法。
package org.example;
import java.util.ArrayList;
import java.util.List;
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("Java Thread执行:" + Thread.currentThread().getName());
}
}
public class App
{
public static void main( String[] args )
{
List<Thread> threads = new ArrayList<>();
// 启动10个线程
for (int i = 0; i < 10; i++) {
threads.add(new Thread(new MyThread()));
}
// 线程,启动!
for (Thread thread : threads) {
thread.start();
}
}
}
进阶实现:Callable 与线程池
基础方式存在明显缺陷:每次创建线程会消耗系统资源,且无法控制并发数量。Callable 接口与线程池的出现,解决了 “有返回值任务” 与 “线程资源管控” 的问题。
Callable接口
Callable 与 Runnable 类似,但提供了两个关键改进:① 任务执行后可返回结果;② 允许抛出受检异常。需配合FutureTask(实现 Future 接口)获取结果;
package org.example;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
return "Java Thread执行:" + Thread.currentThread().getName();
}
}
public class App {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<FutureTask<String>> futures = new ArrayList<>();
// 1. 先启动所有线程(并发执行)
for (int i = 0; i < 10; i++) {
FutureTask<String> future = new FutureTask<>(new MyThread());
futures.add(future);
new Thread(future).start(); // 仅启动线程,不立即获取结果
}
// 2. 所有线程启动后,再统一获取结果(此时任务已并发执行)
for (FutureTask<String> future : futures) {
System.out.println(future.get());
}
}
}
线程池:控制线程资源的核心
线程池是 Java 并发编程的 “最佳实践”,通过预先创建线程、复用线程、控制并发数,避免频繁创建销毁线程的开销。Java 通过java.util.concurrent.Executors提供了多种线程池实现,常用的有FixedThreadPool(固定大小)、CachedThreadPool(缓存线程)、ScheduledThreadPool(定时任务)等。
package org.example;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class App {
public static void main(String[] args) {
// 1. 创建固定大小的线程池(核心线程数=3)
ExecutorService executor = Executors.newFixedThreadPool(3);
// 2. 提交5个任务(线程池会复用3个线程执行)
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("线程池任务" + taskId + "执行:" + Thread.currentThread().getName());
});
}
// 3. 关闭线程池(等待已提交任务完成,不再接受新任务)
executor.shutdown();
}
}为了让你清晰直观地了解Executors提供的所有线程池,我将以表格形式呈现,从创建方法、核心特点、适用场景、风险提示几个关键维度进行梳理,方便你快速对比和参考。
注意:生产环境禁止使用 Executors 工具类创建线程池,需手动创建ThreadPoolExecutor(Java 原生)或ThreadPoolTaskExecutor(Spring),通过显式配置队列容量、最大线程数、拒绝策略,避免资源耗尽风险
理解以下概念是掌握并发编程的关键,也是后续学习 Spring 多线程的基础:
线程状态:Java 线程有 6 种状态(New、Runnable、Blocked、Waiting、Timed Waiting、Terminated),状态转换需通过start()、sleep()、wait()等方法触发。
线程安全:多个线程操作共享资源时,需通过同步机制保证数据一致性,常用方式有synchronized关键字(隐式锁)和Lock接口(显式锁,如ReentrantLock)。
线程通信:通过Object类的wait()、notify()、notifyAll()方法,实现线程间的协作(如生产者 - 消费者模型)。
虚拟线程
虚拟线程(Virtual Thread)是Java19作为预览特性, Java 21 中正式引入的轻量级线程特性,属于 JDK 层面的创新,而非操作系统级线程(平台线程)。它的核心价值是以极低的资源消耗支持百万级并发任务,特别适合 IO 密集型场景(如网络请求、数据库操作等)。“虚拟线程的核心优势在于‘IO 阻塞时自动让出平台线程’,因此仅适合 IO 密集型任务(如网络请求、数据库查询、文件 IO);对于 CPU 密集型任务(如复杂计算、循环处理),虚拟线程无法减少 CPU 占用,且因 JVM 调度虚拟线程需额外开销,性能可能不如传统平台线程池(建议 CPU 密集型任务仍使用固定大小的平台线程池,核心线程数设为 CPU 核心数 ±1)
虚拟线程的核心特性
轻量级资源占用
一个虚拟线程的栈内存初始仅几十 KB,且可动态伸缩,而平台线程通常占用 1-2 MB 栈内存。
单 JVM 可轻松创建数百万个虚拟线程,而平台线程受操作系统进程数限制(通常最多几千个)。
与平台线程的关系
虚拟线程依赖平台线程(操作系统线程)运行,多个虚拟线程可映射到同一个平台线程上执行(M:N 调度)。
当虚拟线程执行 IO 操作(如
socket.read()、sleep())时,会自动 “让出” 绑定的平台线程,供其他虚拟线程使用,避免资源浪费。
编程模型兼容
完全兼容现有的线程 API(
Thread、Runnable、Callable等),无需修改代码逻辑即可迁移。例如,通过
Thread.startVirtualThread()即可创建虚拟线程,用法与平台线程类似。
使用方式:
直接创建虚拟线程(基础用法)
// 方式1:通过 Thread 静态方法创建
Thread virtualThread = Thread.startVirtualThread(() -> {
System.out.println("虚拟线程执行:" + Thread.currentThread());
// 模拟IO操作(如网络请求)
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
virtualThread.join(); // 等待执行完成虚拟线程池
通过 Executors.newVirtualThreadPerTaskExecutor() 创建虚拟线程池,每个任务对应一个虚拟线程:
package org.example;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class App {
public static void main(String[] args) throws InterruptedException {
// 创建虚拟线程池(每个任务一个虚拟线程)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 提交100万个任务(轻松支持,无OOM风险)
for (int i = 0; i < 1_000_000; i++) {
int taskId = i;
executor.submit(() -> {
// 模拟IO操作(如数据库查询)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (taskId % 100_000 == 0) {
System.out.println("任务" + taskId + "执行:" + Thread.currentThread());
}
});
}
} // try-with-resources 自动关闭线程池
}
}Spring多线程:基于Java的封装
Spring 框架并未重新发明多线程,而是在 Java 原生多线程的基础上,通过 “注解驱动”“线程池自动配置”“异常统一处理” 等特性,简化多线程开发。其核心是围绕@Async注解展开,同时支持灵活的线程池配置与事件驱动的异步处理。
核心实现:@Async 注解
@Async是 Spring 多线程最常用的方式,仅需三步即可实现方法的异步执行,底层依赖 Spring AOP 动态代理机制。
开启异步支持:在 Spring 配置类(或启动类)上添加@EnableAsync注解,该注解会导入AsyncConfigurationSelector,注册异步代理处理器(AsyncAnnotationBeanPostProcessor),用于识别@Async注解的方法。
package org.example.springboottest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
定义异步方法:在 Spring 管理的 Bean(如@Service、@Component)的 public 方法上添加@Async注解,该方法会被动态代理拦截,提交到线程池异步执行。
package org.example.springboottest.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
@Slf4j
public class AsyncService {
@Async
public CompletableFuture<Integer> asyncMethod() {
log.info("Execute method asynchronously. "
+ Thread.currentThread().getName());
return CompletableFuture.completedFuture(1);
}
}
调用异步方法:需通过 Spring 容器注入Bean实例调用异步方法(注意:类内部自调用无效,因会绕过动态代理)。当异步方法有返回值时,如何获取异步方法执行的返回结果呢?这时需要异步调用的方法带有返回值CompletableFuture。
CompletableFuture是 JDK 8+ 对Future的增强,支持 “异步任务组合”(如thenApply/thenCombine)、“异常处理”(exceptionally)、“非阻塞获取结果”(whenComplete),是处理复杂异步逻辑的关键工具。
package org.example.springboottest.controller;
import lombok.extern.slf4j.Slf4j;
import org.example.springboottest.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private AsyncService asyncService;
@GetMapping
public String test() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> result = asyncService.asyncMethod();
log.info("result: " + result.get());
return "test";
}
}
Spring虚拟线程池(Spring Framework 6.1+)
配置线程池说明见下文:关键配置
@Bean
public Executor taskExecutor() {
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("spring-virtual-");// 线程前缀名
return executor;
}全局Async异常处理器
在 Spring 多线程开发中,@Async 标注的异步方法若抛出未捕获异常(对于无返回值的异步方法),默认仅会打印日志且无法触发业务级异常处理(如告警、数据补偿)。全局 Async 异常处理器的核心作用是:统一捕获所有异步方法的未处理异常,集中实现日志记录、业务告警、异常恢复等逻辑,避免异常分散导致的运维困难。
方式 1:实现 AsyncConfigurer 接口(集成线程池配置)
通过实现 AsyncConfigurer 接口,可同时配置自定义线程池和全局异常处理器,逻辑更聚合,适合需要统一管理线程池与异常处理的场景。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync // 必须开启异步支持
public class AsyncGlobalConfig implements AsyncConfigurer {
/**
* 1. 配置自定义线程池(生产环境必配,避免默认线程池的资源风险)
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 基础参数配置
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(100); // 任务队列容量(非默认,避免OOM)
executor.setKeepAliveSeconds(300); // 非核心线程空闲时间
executor.setThreadNamePrefix("Biz-Async-"); // 线程名前缀(便于日志排查)
// 关键配置:拒绝策略(核心业务用CallerRunsPolicy,避免任务丢失)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 关键配置:关闭策略(等待已提交任务完成,避免任务中断)
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(120); // 等待超时时间
executor.initialize(); // 初始化线程池
return executor;
}
/**
* 2. 配置全局 Async 异常处理器
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// 自定义异常处理逻辑(Lambda实现接口)
return (throwable, method, params) -> {
// 步骤1:详细日志记录(包含方法名、参数、异常栈,便于排查)
System.err.printf(
"【Async全局异常】方法名:%s,入参:%s,异常类型:%s,异常信息:%s%n",
method.getName(), // 异常方法名
Arrays.toString(params), // 方法入参(需注意敏感数据脱敏)
throwable.getClass().getSimpleName(), // 异常类型
throwable.getMessage() // 异常信息
);
// 打印异常栈(关键:避免只看信息无法定位代码行)
throwable.printStackTrace();
// 步骤2:业务级异常处理(根据实际场景扩展)
// 示例1:核心业务异常触发告警(如调用告警接口、发送邮件/短信)
if (throwable instanceof RuntimeException) {
// alertService.sendAlert("Async核心业务异常:" + throwable.getMessage());
}
// 示例2:异常数据补偿(如标记任务状态为“失败”,便于后续重试)
// if (params[0] instanceof Long taskId) {
// taskService.markTaskFailed(taskId, throwable.getMessage());
// }
};
}
}方式 2:直接注册 AsyncUncaughtExceptionHandler Bean(简洁,适合已有线程池配置)
若项目中已单独配置线程池(如通过 @Bean 定义 Executor),可直接注册 AsyncUncaughtExceptionHandler 类型的 Bean,Spring 会自动将其作为全局异常处理器。
@Bean
public AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler() {
return (throwable, method, params) -> {
// 1. 日志记录
log.error(
"【Async全局异常】方法:%s,入参:%s,异常:%s%n",
method.getName(),
Arrays.toString(params),
throwable.getMessage()
);
throwable.printStackTrace();
// 2. 业务扩展(如告警、补偿)
// ...
};
}注意事项
若异步方法返回 Future 或 CompletableFuture(有返回值),异常会被封装到返回值中,不会触发全局异常处理器,需通过返回值主动处理:
package org.example.springboottest.controller;
import lombok.extern.slf4j.Slf4j;
import org.example.springboottest.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private AsyncService asyncService;
@GetMapping
public String test() throws ExecutionException, InterruptedException {
asyncService.asyncMethod().whenComplete((result, ex) -> {
if (ex != null) {
// 处理异常(日志、告警等)
log.error("有返回值异步异常:" + ex.getMessage());
}
log.info("result: " + result);
});;
return "test";
}
}关键优化
@Async默认使用 Spring 内置的SimpleAsyncTaskExecutor(每次创建新线程,无资源控制,不适合生产环境)。因此,Spring 支持通过配置Executor Bean,自定义线程池参数,实现线程资源的精细化管控。
自定义线程池配置
通过ThreadPoolTaskExecutor(Spring 对ThreadPoolExecutor的封装)配置核心线程数、最大线程数、队列容量等参数,并指定线程池名称,供@Async注解引用。
package org.example.springboottest.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 基础参数
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(300);
executor.setThreadNamePrefix("Spring-Async-");
// 新增:拒绝策略(核心业务优先用CallerRunsPolicy,避免任务丢失)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 新增:关闭策略(等待已提交任务完成,超时120秒)
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(120);
executor.initialize();
return executor;
}
}
为帮助你清晰掌握ThreadPoolTaskExecutor的可配置项,我将结合文档中Spring多线程的实践场景,以表格形式梳理各配置项的名称、说明、默认值及配置建议,让你能根据业务需求合理设置参数。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Arrays;
@Configuration
@EnableAsync
public class AsyncExceptionConfig {
// 注册全局 Async 异常处理器(Spring自动识别)
@Bean
public AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler() {
return (throwable, method, params) -> {
// 1. 日志记录
System.err.printf(
"【Async全局异常】方法:%s,入参:%s,异常:%s%n",
method.getName(),
Arrays.toString(params),
throwable.getMessage()
);
throwable.printStackTrace();
// 2. 业务扩展(如告警、补偿)
// ...
};
}
// 已有的自定义线程池(若有)
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("Simple-Async-");
executor.initialize();
return executor;
}
}@Async 注解核心原理拆解
在 Spring 多线程体系中,@Async能让方法实现异步执行,核心依赖Spring AOP 动态代理与线程池任务调度的协同,其原理可拆解为 “开启支持 - 代理创建 - 任务提交 - 异步执行” 四大关键步骤,以下结合文档内容详细解析:
一、前提:@EnableAsync 开启异步支持
@Async生效的第一步是通过@EnableAsync注解激活 Spring 的异步能力,其底层逻辑如下:
导入配置类:@EnableAsync会自动导入AsyncConfigurationSelector类,该类会根据 Spring 环境选择默认的异步配置(如ProxyAsyncConfiguration);
注册核心处理器:配置类会注册AsyncAnnotationBeanPostProcessor(Bean 后置处理器),这个处理器是识别@Async注解的 “核心探测器”,会在 Spring 容器初始化 Bean 时,扫描 Bean 中带有@Async的方法;
初始化默认线程池:若未自定义线程池,Spring 会默认初始化SimpleAsyncTaskExecutor(每次执行任务创建新线程,无资源管控,仅适用于测试场景)。
二、核心:动态代理拦截异步方法
Spring 通过动态代理实现对@Async方法的拦截,这是异步执行的核心技术支撑,具体流程如下:
代理对象创建:当AsyncAnnotationBeanPostProcessor扫描到 Bean 中存在@Async方法时,会为该 Bean 创建动态代理对象(默认优先 JDK 动态代理,若 Bean 未实现接口则使用 CGLIB 代理);
方法调用拦截:当外部通过 Spring 容器注入的 Bean(实际是代理对象)调用@Async方法时,代理对象会拦截该调用,不会直接执行原方法逻辑;
任务封装:代理对象会将原方法的执行逻辑、参数等信息,封装为Runnable(无返回值)或Callable(有返回值)任务对象,准备提交到线程池。
三、执行:线程池调度任务异步运行
代理对象封装完任务后,会将任务提交到指定线程池,实现 “调用方立即返回,任务异步执行”,具体逻辑如下:
线程池选择:
若@Async注解指定了线程池名称(如@Async("springAsyncPool")),则优先使用该名称对应的Executor Bean;
若未指定,会查找容器中默认的Executor Bean(需符合特定命名规则);
若未找到自定义线程池,使用 Spring 默认的SimpleAsyncTaskExecutor;
任务提交与执行:
线程池接收到任务后,会根据自身状态(核心线程是否空闲、队列是否已满)分配线程执行任务;
若核心线程空闲,直接用核心线程执行;若核心线程满且队列未满,任务入队等待;若队列满且未达最大线程数,创建非核心线程执行;
调用方返回:任务提交到线程池后,代理对象会立即向调用方返回结果(无返回值返回null,有返回值返回Future对象,用于后续获取任务结果),调用方无需等待任务执行完成。
四、关键组件:支撑异步逻辑的核心类
@Async的运行依赖多个 Spring 核心类,各组件分工明确:
AsyncAnnotationBeanPostProcessor:Bean 后置处理器,负责扫描@Async方法并创建代理对象;
AsyncTaskExecutor:Spring 定义的异步任务执行器接口,ThreadPoolTaskExecutor(文档中自定义线程池使用的类)是其常用实现,封装了 JDK 的ThreadPoolExecutor;
AsyncResult:用于封装有返回值异步任务的结果,实现Future接口,支持get()方法获取结果(会阻塞直到任务完成);
AsyncUncaughtExceptionHandler:无返回值异步任务的异常处理器,当任务执行抛出未捕获异常时,会通过该处理器统一处理(如日志记录、告警)。
五、原理延伸:为何自调用会失效?
@Async基于Sring AOP来实现,为帮你快速掌握 Spring AOP 的局限性及失效场景,我会结合之前提到的动态代理(JDK/CGLIB)知识,以简洁的要点形式梳理
基于动态代理,仅能代理 Spring 容器管理的 Bean
非 Spring Bean(如手动
new的对象、JDK 原生类)无法被 AOP 拦截,因未经过 Spring 代理创建流程。
无法拦截静态方法、final 方法、private 方法
静态方法属于类级别的方法,动态代理(JDK/CGLIB)仅能代理实例方法;
final 方法无法被重写(CGLIB 基于子类代理,无法重写 final 方法);
private 方法访问权限限制,动态代理无法获取并拦截。
自调用(类内部方法调用)场景失效
同一类中,A 方法调用本类的 B 方法(B 方法被 AOP 增强)时,调用的是 “原始对象” 的 B 方法,绕过了代理对象,导致 AOP 逻辑不触发(如
@Async自调用失效)。
无法处理 JVM 层面的原生方法或特殊类
如
Object类的wait()、notify()等原生方法,或String等不可变类,AOP 无法拦截其方法调用。