You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:17:53