Java 异步编程实战:Thread、线程池、CompletableFuture、@Async 用法与场景
在平常的软件开发工作中,我们总会遇到一些特殊的功能场景,比如系统操作日志的异步记录、海量数据的文件导出、多维度报表的生成任务等。这些功能往往具有一个共同的特点:执行过程耗时较长,如果采用传统的同步处理方式,会导致用户界面卡顿、接口响应超时,甚至占用核心业务线程资源,影响整个系统的运行效率与稳定性。这个时候就需要引入异步了,那么常见的实现异步的方式有哪些呢?今天来简单总结下。
一:@Async 注解
1:使用场景:
(1):记录系统日志
(2):非实时通知推送
(3):缓存异步刷新
2:示例:
@Async
public void logActionAsync(String userId, String context) {try {// 模拟日志写入耗时(如写数据库、文件)Thread.sleep(1000);System.out.println("【异步线程】日志记录完成");} catch (InterruptedException e) {e.printStackTrace();}
}@SpringBootApplication(scanBasePackages = {"cn.xxx.xxxx"})
@EnableAsync
public class StarterApplication {public static void main(String[] args) {SpringApplication.run(StarterApplication.class, args);}
}
二:线程池
1:使用场景:
(1):接口高并发异步处理
(2):定时任务集中调度
(3):计算密集型任务处理
2:示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;@Configuration
public class TaskThreadPoolConfig {// 核心线程数,一般根据业务并发量等合理设置,这里假设设为5private static final int CORE_POOL_SIZE = 5;// 最大线程数,当任务过多时可临时扩充的线程数量,设为10private static final int MAXIMUM_POOL_SIZE = 10;// 线程空闲时间,单位为秒,空闲超过这个时间的线程会被回收,设为60秒private static final int KEEP_ALIVE_TIME_SECONDS = 60;// 阻塞队列,用于存放等待执行的任务,这里使用LinkedBlockingQueue,容量设为100private static final BlockingQueue<Runnable> WORK_QUEUE = new LinkedBlockingQueue<>(100);@Bean("taskThreadPoolExecutor")public ThreadPoolExecutor taskThreadPoolExecutor() {return new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME_SECONDS,TimeUnit.SECONDS,WORK_QUEUE,// 线程工厂,可自定义线程的创建方式、名称等,这里使用默认的ThreadPoolExecutor.defaultThreadFactory(),// 拒绝策略,当线程池和队列都满了后如何处理新任务,这里采用调用者运行策略new ThreadPoolExecutor.CallerRunsPolicy());}
}
调用:
@Service
public class AsyncLogService {@Autowiredprivate LogService logService;@Autowiredprivate ThreadPoolExecutor taskThreadPoolExecutor;// 提交异步任务public void submitLogTask(String userId, String context) {taskThreadPoolExecutor.execute(() -> logService.logActionAsync(userId, context));}}
三:Thread + Callable
1:使用场景:
(1):独立数据查询与计算
(2):第三方接口异步调用
2:示例:
@Service
public class AsyncLogService {@Autowiredprivate LogService logService;public void saveInfo() {// 无返回值(Runnable)new Thread(() -> {try {logService.logActionAsync("1", "新增");System.out.println("Thread 异步任务执行完成");} catch (Exception e) {e.printStackTrace();}}).start();}public String saveInfoResult() throws ExecutionException, InterruptedException {Callable<String> task = () -> {logService.logActionAsync("1", "新增");return "success";};FutureTask<String> futureTask = new FutureTask<>(task);new Thread(futureTask).start();String result = futureTask.get();return result;}}
四:CompletableFuture
1:使用场景
(1):多任务串行依赖
(2):任务组合与结果转换
(3):多任务并行执行
2:示例:
@Service
public class AsyncLogService {@Autowiredprivate LogService logService;public void saveInfo() throws ExecutionException, InterruptedException {// 1. 无返回值的异步任务(runAsync)CompletableFuture<Void> noResultFuture = CompletableFuture.runAsync(() -> {// 业务逻辑处理logService.logActionAsync("11", "新增");System.out.println("无返回值任务执行结束");});// 2. 有返回值的异步任务(supplyAsync)CompletableFuture<String> resultFuture = CompletableFuture.supplyAsync(() -> {// 业务逻辑处理String result = logService.logActionAsync("11", "新增");System.out.println("有返回值任务执行结束");return result;});// 等待无返回值任务完成noResultFuture.get();// 等待有返回值任务完成并获取结果String result = resultFuture.get();}}
五:总结
以上为常见的几种实现异步的方式。简单日志记录可用@Async,计算密集型任务适合定制线程池,复杂业务流程推荐CompletableFuture。掌握多种异步实现方式,能够让我们在系统性能和代码可维护性之间找到最佳平衡点。