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
相关产品推荐
相关产品推荐

