Spring Boot中如何正确实现线程安全的@Async?解决死锁与异步失效
解决异步任务死锁同时保留并发优势的方案
你的问题核心是用了全局synchronized锁,把所有异步请求强行串行,完全浪费了@Async的并发优势。真正要解决的是让相同myId的请求串行执行,不同myId的请求并行执行,这样既避免数据库资源竞争,又保留异步的性能优势。以下是几种可行的优化方案:
一、基于myId的细粒度锁(推荐)
1. 使用Guava Striped分段锁
通过分段锁机制,让相同myId的请求共享同一把锁,不同myId的请求可以并行操作:
@Service public class MyTask { // 创建16个锁分段,根据myId哈希分配对应锁(数量可根据并发量调整) private final Striped<Lock> stripedLock = Striped.lock(16); @Transactional(propagation = Propagation.REQUIRES_NEW) public void execute(Integer myId) { Lock lock = stripedLock.get(myId); lock.lock(); try { someCalc(myId); // 数据库操作逻辑 } finally { lock.unlock(); } } }
依赖说明:需要引入Guava依赖(Maven):
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>32.1.3-jre</version> </dependency>
2. 自定义ConcurrentHashMap实现锁
如果不想引入第三方依赖,可以用ConcurrentHashMap自己实现细粒度锁:
@Service public class MyTask { private final ConcurrentHashMap<Integer, Lock> lockMap = new ConcurrentHashMap<>(); @Transactional(propagation = Propagation.REQUIRES_NEW) public void execute(Integer myId) { // 确保同一个myId只创建一把锁 Lock lock = lockMap.computeIfAbsent(myId, k -> new ReentrantLock()); lock.lock(); try { someCalc(myId); // 数据库操作逻辑 } finally { lock.unlock(); // 可选:如果myId是一次性请求,用完移除锁避免内存泄漏 // lockMap.remove(myId); } } }
注意:如果myId范围无限大,需考虑定期清理无效锁,防止内存溢出。
二、数据库层面优化(从根源减少死锁)
死锁本质是数据库资源的循环竞争,优化DB操作可以从根源降低死锁概率:
- 确保SQL命中索引:更新/删除操作的
WHERE条件必须命中索引,让数据库使用行锁而非表锁(比如myId作为主键或唯一索引)。 - 调整事务隔离级别:将默认的
REPEATABLE_READ改为READ_COMMITTED,该隔离级别下死锁概率更低,多数业务场景也能满足一致性要求。 - 统一操作顺序:如果多个线程涉及多表/多行操作,所有线程必须遵循相同的操作顺序(比如先更新表A再更新表B),避免循环等待导致死锁。
三、死锁重试机制(适合冲突低频场景)
如果死锁不是高频发生,可以通过重试机制自动恢复,无需额外加锁:
@Service public class MyTask { private static final int MAX_RETRY_TIMES = 3; @Transactional(propagation = Propagation.REQUIRES_NEW) public void execute(Integer myId) { int retryCount = 0; while (retryCount < MAX_RETRY_TIMES) { try { someCalc(myId); // 数据库操作逻辑 return; } catch (CannotAcquireLockException e) { retryCount++; // 重试前短暂休眠,避免立即重试再次冲突 try { Thread.sleep(100 * retryCount); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } if (retryCount == MAX_RETRY_TIMES) { throw e; // 超过重试次数,抛出异常 } } } } }
四、拆分计算与事务
将耗时的计算逻辑移出事务范围,缩短事务持有锁的时间,减少冲突概率:
@Service public class MyTask { @Autowired private MyRepository myRepository; public void execute(Integer myId) { // 先执行耗时计算,无需锁和事务 CalculationResult result = someCalc(myId); // 仅对数据库操作开启事务,锁持有时间大幅缩短 doDbUpdate(myId, result); } @Transactional(propagation = Propagation.REQUIRES_NEW) protected void doDbUpdate(Integer myId, CalculationResult result) { myRepository.updateData(myId, result); } }
内容的提问来源于stack exchange,提问作者user2304483
相关产品推荐
相关产品推荐

