不推荐且不起作用,因synchronized仅限单jvm内线程互斥,无法跨线程、跨实例、跨节点协调,高并发下性能差且存在竞态漏洞;真正有效方案是基于唯一标识+redis共享存储+原子校验(如setnx)的幂等控制。

在Web拦截器Filter中直接用synchronized防止重复请求,**不推荐,也不起作用**。
原因很实在:Filter是每个HTTP请求都会创建一个独立线程调用doFilter方法,而synchronized锁的是对象实例(比如this或某个类.class),它只能保证**同一台服务器、同一个Filter实例内**的串行执行。但Web应用通常是多线程、多实例甚至多节点部署,synchronized既无法跨线程有效协调(尤其高并发下性能急剧下降),更无法跨JVM或分布式环境生效。简单说:它锁不住真实场景下的“重复请求”。
真正管用的思路是“状态识别+原子校验”
核心不是拦住请求进来,而是快速判断“这个请求是否已被处理过”。关键靠三样东西:唯一标识、共享存储、原子操作。
- 唯一标识:通常由客户端携带(如token)或服务端生成(如基于用户ID+接口路径+参数摘要),确保相同业务逻辑产生相同key
- 共享存储:必须是所有请求都能访问的公共空间,Redis最常用——支持过期、原子setnx、分布式可见
- 原子校验:用Redis的SETNX(或RedisTemplate.opsForValue().setIfAbsent)一次完成“存且判是否存在”,避免查+存的竞态漏洞
Filter里该怎么做(精简版)
以登录请求为例,在Filter中做token校验:
- 从request中提取前端传来的token参数(如request.getParameter("token"))
- 构造redis key,例如"repeat:login:" + userId + ":" + token
- 调用redis.set(key, "1", 5, TimeUnit.SECONDS) 或 setIfAbsent + expire组合
- 如果返回false(已存在),说明该token已被消费,直接response.sendError(400, "重复提交")并return
- 如果返回true,放行chain.doFilter(...),后续业务处理完无需手动删key——靠自动过期即可
为什么不用synchronized替代Redis
哪怕你把整个doFilter方法用synchronized(this)包起来:
- 同一台机器上,多个请求仍可能因线程调度间隙造成“查无、写入、再查无、再写入”
- 集群部署时,不同服务器上的Filter互不感知,完全无效
- 锁粒度粗,所有请求排队,吞吐量断崖式下跌,用户等得慌
- 一旦出现异常未释放锁(虽少见),可能导致整个Filter卡死
补充一个轻量级兜底方案(单机适用)
如果项目极小、纯单机、无Redis,可用ConcurrentHashMap + 时间戳清理,但仅作临时过渡:
- 定义static final ConcurrentHashMap
requestCache = new ConcurrentHashMap() - key为请求标识,value为System.currentTimeMillis()
- putIfAbsent后检查是否已存在,且存在时判断时间是否超5秒,超则覆盖
- 需额外启一个ScheduledExecutorService定期清理过期项,否则内存泄漏
这比synchronized安全,但仍不具备分布式能力,生产环境务必上Redis。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











