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

SpringBoot如何实现@Service类方法同一时间仅允许单线程访问

问题原因说明

你不需要使用static synchronized实现单线程访问效果,这个方案本身就不适用于Spring Bean的场景:

  • static synchronized锁的是类的Class对象,虽然能实现互斥,但静态同步块属于类级别上下文,无法直接访问实例级别的@Autowired注入成员,强行调用会触发空指针。
  • Spring中@Service默认是单例模式,整个应用上下文里只会存在一个Saver实例,用实例级别的锁完全可以达到和static synchronized一致的单实例内互斥效果,同时可以正常访问注入的Bean。
可行实现方案

方案1:单实例部署用实例级synchronized(最简单)

如果你的服务是单节点部署,直接给方法加非静态的synchronized关键字即可,锁的是当前单例Bean实例,所有调用process方法的线程都会争抢同一把锁,完全满足单线程访问要求。

@Service
public class Saver implements DataSaver {

    @Autowired
    private SaverLogic saverLogic;
    
    
    @Override
    public synchronized String process(...) {
        Future<String> result = saverLogic.save(repositoryContainer, entities);
        // 若要求异步save方法执行完成后才允许下一个线程进入,必须放开下面这行
        // return result.get();
        // 若仅要求提交异步任务的动作串行,不需要等待任务执行完,直接返回即可
        return "task submitted";
    }
}

注意点

该方案仅能保证单实例内的串行访问,如果是多节点集群部署,不同实例的锁互不影响,会出现跨实例并发,这种场景需要用分布式锁。

方案2:用ReentrantLock显式锁(灵活性更高)

如果需要更细粒度的锁控制(比如公平锁、加锁超时、可中断加锁),推荐使用JDK自带的ReentrantLock,比synchronized更灵活,同样是实例级锁,适配单例Bean场景。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class Saver implements DataSaver {

    @Autowired
    private SaverLogic saverLogic;

    // 构造参数传true表示启用公平锁,按线程等待顺序获取锁,避免饥饿;不需要可传false
    private final ReentrantLock processLock = new ReentrantLock(true);
    
    
    @Override
    public String process(...) {
        // 支持设置加锁超时时间,比如尝试5秒没拿到锁就抛异常,避免死等
        boolean locked = false;
        try {
            locked = processLock.tryLock(5, TimeUnit.SECONDS);
            if (!locked) {
                throw new RuntimeException("系统繁忙,请稍后重试");
            }
            Future<String> result = saverLogic.save(repositoryContainer, entities);
            // 同样,需要等异步任务执行完再放行就加下面的超时等待
            return result.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("process执行异常", e);
        } finally {
            if (locked) {
                processLock.unlock(); // 必须在finally中释放锁,避免异常导致锁永久占用
            }
        }
    }
}

方案3:集群部署场景用分布式锁

如果服务是多节点集群部署,需要全局严格串行,需要使用分布式锁实现,常见实现有:

  • 基于Redis的Redisson可重入锁
  • 基于ZooKeeper的顺序临时节点锁
  • 基于数据库唯一约束的锁
    单实例部署不推荐使用,会增加不必要的复杂度。
针对@Async方法的特殊注意事项

你注入的SaverLogic中save方法带@Async注解,Spring会通过代理把该方法提交到独立线程池执行,调用后会立刻返回Future对象,不会阻塞等待save逻辑执行完成:

  • 如果你的需求是整个save逻辑执行完毕前,不允许其他线程进入process,必须在锁持有范围内调用future.get()等待异步任务执行完成,否则提交完任务就会释放锁,下一个线程会立刻进入process,达不到互斥效果。
  • 如果你的需求只是提交异步任务的动作串行,不需要等待save实际执行完成,不需要调用get()方法,提交完任务即可释放锁。

内容的提问来源于stack exchange,提问作者Dui Samarasinghe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:48:20