Java-在使用 @Async 注解的方法时,分布式锁管理和释放

在使用 @Async 注解的方法时,确保分布式锁能够正确地管理和释放,需要特别注意异步方法的

执行和锁的释放时机。以下是一个完整的示例,展示了如何在 @Async 注解的方法中正确使用分

布式锁。

1. 添加依赖

首先,确保你的项目中包含了必要的依赖项,例如 Spring Boot 和 Redisson。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.17.6</version>
    </dependency
<think>我们参考用户提供的引用资料,用户希望不使用@Async注解,在Spring Boot中整合线程池CountDownLatch实现批量业务处理并返回处理结果。 引用[1][2]提到了@Async使用任务执行器的配置,但用户要求不使用@Async,因此我们需要手动创建使用线程池。 引用[4]中有一个使用@Async注解并指定线程池的例子,同使用了CountDownLatch来等待所有线程完成。我们可以借鉴这个CountDownLatch的用法,但去掉@Async注解,改为手动提交任务到线程池。 实现思路: 1. 在Spring Boot中配置一个线程池(可以使用ThreadPoolTaskExecutor)。 2. 将批量任务拆分成多个子任务,提交到线程池中执行。 3. 使用CountDownLatch来等待所有子任务完成。 4. 收集每个子任务的处理结果(可以使用Future或者自定义结果收集器)。 步骤: 步骤1:配置线程池 我们可以通过@Configuration@Bean来定义一个线程池。 步骤2:编写任务执行方法 将需要异步执行的业务逻辑封装成Runnable或Callable任务。 步骤3:提交任务并管理 在需要批量处理的方法中,创建CountDownLatch,数量为任务数。然后循环提交任务到线程池,每个任务执行完后将countDown。同,为了获取结果,我们可以使用CallableFuture。 步骤4:等待所有任务完成并收集结果 使用CountDownLatch.await()等待所有任务完成,然后从Future中取出结果。 示例代码: 1. 配置线程池(在配置类中): ```java @Configuration public class ThreadPoolConfig { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(20); // 队列大小 executor.setQueueCapacity(200); // 线程名称前缀 executor.setThreadNamePrefix("task-"); // 拒绝策略 CallerRunsPolicy:当线程池没有处理能力的候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } } ``` 2. 定义一个服务类,用于执行批量任务: ```java @Service @Slf4j public class BatchProcessService { @Autowired @Qualifier("taskExecutor") private Executor taskExecutor; /** * 批量处理方法 * @param tasks 任务列表(每个任务是一个Callable,返回结果) * @return 每个任务的处理结果列表(按顺序) */ public <T> List<T> batchProcess(List<Callable<T>> tasks) throws InterruptedException { int taskCount = tasks.size(); // 创建CountDownLatch,用于等待所有任务完成 CountDownLatch countDownLatch = new CountDownLatch(taskCount); // 用于保存Future的列表 List<Future<T>> futures = new ArrayList<>(taskCount); // 用于保存结果的列表 List<T> results = new ArrayList<>(taskCount); // 遍历任务列表,提交到线程池 for (Callable<T> task : tasks) { // 将每个任务包装一下,使其在完成后调用countDownLatch.countDown() Callable<T> wrappedTask = () -> { try { return task.call(); } finally { countDownLatch.countDown(); } }; futures.add(taskExecutor.submit(wrappedTask)); } // 等待所有任务完成 countDownLatch.await(); // 获取每个任务的结果 for (Future<T> future : futures) { try { results.add(future.get()); } catch (ExecutionException e) { log.error("任务执行异常", e); // 根据需求,可以抛出异常或者设置默认值 // 这里我们抛出一个运行异常 throw new RuntimeException(e); } } return results; } } ``` 3. 在业务中使用: 假设我们有一个批量插入用户的任务,我们可以这样调用: ```java @Service public class UserService { @Autowired private BatchProcessService batchProcessService; @Autowired private UserMapper userMapper; public List<Boolean> batchInsertUsers(List<User> userList) { // 将每个用户的插入操作封装成一个Callable任务 List<Callable<Boolean>> tasks = new ArrayList<>(); for (User user : userList) { tasks.add(() -> { try { // 这里执行插入操作,返回是否成功 int count = userMapper.insert(user); return count > 0; } catch (Exception e) { // 处理异常 return false; } }); } // 调用批量处理服务 try { return batchProcessService.batchProcess(tasks); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("批量插入用户被中断", e); } } } ``` 注意: 1. 上述BatchProcessService中,我们在每个任务执行完成后调用countDownLatch.countDown(),这样无论任务正常完成还是异常,都会计数减一。 2. 在获取结果,我们按Future列表的顺序获取,注意这个顺序任务提交顺序一致,但实际执行顺序可能不同。不过由于我们使用CountDownLatch等待所有任务完成,所以可以安全地获取结果。 3. 如果需要结果的顺序提交顺序一致,那么使用Future列表按顺序获取即可。 另一种做法是不用CountDownLatch,而是通过遍历Future.get()来等待所有任务完成(因为Future.get()是阻塞的)。但是这样会按顺序等待,如果前面的任务执行间长,即使后面的任务先完成也要等待前面的任务完成才能获取结果。而使用CountDownLatch可以同等待所有任务完成,然后再按顺序获取结果,这样效率更高。 所以,我们使用CountDownLatch来同等待所有任务完成,然后再获取结果。 用户要求返回处理结果,所以我们使用Callable来返回结果,而不是Runnable。 引用[4]中使用了CountDownLatch,并在finally中调用countDown(),我们也采用了类似的做法,确保每个任务无论成功失败都会调用countDown()。 综上,我们通过手动配置线程池,手动提交任务,结合CountDownLatchFuture,实现了不使用@Async注解的批量处理并返回结果的功能。 </think>在Spring Boot中不使用`@Async`注解实现线程池与`CountDownLatch`的整合,可通过以下步骤实现批量业务处理及结果返回: ### 实现步骤及代码示例 #### 1. 配置自定义线程池 ```java @Configuration public class ThreadPoolConfig { @Bean("customThreadPool") public ExecutorService customThreadPool() { return new ThreadPoolExecutor( 10, // 核心线程数 20, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活间 new LinkedBlockingQueue<>(100), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); } } ``` #### 2. 实现批量处理服务 ```java @Service public class BatchService { @Resource(name = "customThreadPool") private ExecutorService threadPool; /** * 批量处理方法 * @param dataList 待处理数据集合 * @return 包含每个任务处理结果的Map */ public Map<String, Object> processBatch(List<Object> dataList) { CountDownLatch latch = new CountDownLatch(dataList.size()); Map<String, Object> resultMap = new ConcurrentHashMap<>(); // 提交所有任务到线程池 for (Object data : dataList) { threadPool.submit(() -> { try { Object result = processSingle(data); // 业务处理 resultMap.put(data.getId().toString(), result); } catch (Exception e) { resultMap.put(data.getId().toString(), "处理失败: " + e.getMessage()); } finally { latch.countDown(); // 计数减一 } }); } // 等待所有任务完成 try { latch.await(30, TimeUnit.SECONDS); // 设置超间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("任务等待被中断", e); } return resultMap; } private Object processSingle(Object data) { // 单个业务处理逻辑 return "处理成功: " + data.hashCode(); } } ``` ### 关键设计说明 1. **线程池管理**: - 使用`ThreadPoolExecutor`显式配置线程池参数 - 通过`@Resource`注入自定义线程池实例 2. **任务调度机制**: ```mermaid graph LR A[主线程] --> B[初始化CountDownLatch] B --> C[提交任务到线程池] C --> D{任务处理} D -->|成功| E[保存结果] D -->|失败| F[记录异常] E & F --> G[计数减一] G --> H{所有任务完成?} H -->|是| I[返回聚合结果] H -->|超| J[中断处理] ``` 3. **结果返回设计**: - 使用`ConcurrentHashMap`保证线程安全 - 通过`CountDownLatch.await()`实现主线程阻塞等待 - 设置超间防止死锁 4. **异常处理**: - 单个任务异常捕获不影响整体执行 - 主线程中断恢复处理(`Thread.currentThread().interrupt()`) ### 业务调用示例 ```java @RestController public class BatchController { @Autowired private BatchService batchService; @PostMapping("/batch-process") public ResponseEntity<Map<String, Object>> handleBatchRequest(@RequestBody List<Object> data) { Map<String, Object> results = batchService.processBatch(data); return ResponseEntity.ok(results); } } ``` ### 注意事项 1. **资源管理**: - 线程池需根据业务负载调整参数,避免OOM - 推荐使用`ThreadPoolTaskExecutor`替代原生`ThreadPoolExecutor`以获得Spring管理特性 2. **超控制**: - `latch.await()`必须设置超-后应主动中断线程(`threadPool.shutdownNow()`) 3. **结果一致性**: - 对于需要严格顺序的场景,可通过`Future`收集结果: ```java List<Future<?>> futures = new ArrayList<>(); futures.add(threadPool.submit(...)); for (Future<?> f : futures) { f.get(); // 按提交顺序获取结果 } ``` 此方案避免了`@Async`的AOP代理限制,特别适合需要精确控制线程生命周期的场景,如批量数据迁移、分布式计算等[^4]。 --- **相关问题:** 1. 如何防止线程池任务堆积导致内存溢出? 2. `CountDownLatch``CyclicBarrier`在批量处理中的区别? 3. 如何实现异步任务的结果回调机制? 4. Spring Boot中如何监控线程池运行状态?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHOU_VIP

您的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值