应避免用String常量或拼接字符串作synchronized锁,因其受常量池和对象创建机制影响易导致锁失效或过重;推荐用ConcurrentHashMap缓存唯一锁对象,必要时结合分布式锁兜底。

Java 中用 synchronized 对 String 常量加锁,表面看能按业务维度(如用户 ID、订单号)做隔离,实际却极易踩坑:要么锁失效(不同线程拿不到同一把锁),要么锁过重(多个无关业务被意外串行)。核心问题不在 synchronized 本身,而在 String 的常量池机制和对象引用语义。避开陷阱的关键是——不依赖字符串内容自动复用,而主动、可控地管理锁对象生命周期。
别用字面量或运行时拼接的 String 当锁
像 synchronized("user_" + userId) 或 synchronized(userId + "") 这类写法,看似按用户隔离,实则危险:
- 编译期确定的拼接(如
"user_" + "123")会被优化为字面量"user_123",进入常量池 → 多个模块可能无意共用同一锁 - 运行时拼接(如
"user_" + userId,其中 userId 是变量)大概率生成堆中新 String 对象 → 每次 new 出不同实例,synchronized 形同虚设 - String.valueOf()、toString() 等方法返回的也多是新对象,不进常量池
慎用 intern() 强制入池
synchronized(userId + "".intern()) 能让等值字符串指向同一对象,但代价很高:
- JDK 7+ 后
intern()存入堆的老年代,高频调用会快速堆积,触发频繁 Full GC -
intern()方法本身是同步的,高并发下成为新的全局竞争点 - 不同 ClassLoader 加载的同名字符串,
intern()后仍不等价,跨模块时可能漏锁
推荐用 ConcurrentHashMap 缓存锁对象
这是最常用、平衡性最好的方案:按业务键(如 userId)动态生成唯一锁对象,并复用:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- 使用
ConcurrentHashMap.computeIfAbsent(key, k -> new Object()),保证每个 key 对应唯一锁实例 - 锁对象是轻量级
new Object(),无内存泄漏风险;map 可配合定时清理或弱引用控制大小 - 示例:
Object lock = locks.computeIfAbsent(userId, k -> new Object()); synchronized(lock) { ... }
考虑更上层的分布式锁兜底
单机 synchronized 只适合本地快速拦截,无法解决集群多实例问题:
- 秒杀、扣减等强一致性场景,必须搭配数据库行锁(
SELECT ... FOR UPDATE)或 Redis Lua 脚本实现最终原子性 - 可组合使用:先用 ConcurrentHashMap 锁做本地限流,再用 Redis 锁做跨节点协调,最后由 DB 持久化校验
- 避免把所有压力压在 JVM 内存锁上,尤其当 ID 维度多、QPS 高时
不复杂但容易忽略:锁的本质是对象引用,不是字符串值;真正安全的锁策略,一定是你亲手创建、明确持有、可控销毁的。

















