字符串字面量不能作为可靠锁,因其被全局共享易导致意外互斥、运行时拼接无法入常量池致锁失效,且intern()有OOM和性能风险;应改用ConcurrentHashMap缓存锁对象或分片锁等安全策略。

Java 中用字符串常量池对象作 synchronized 锁看似能复用同一实例,但实际极易踩坑——不是锁不住,而是锁得太宽、太慢、太危险。
为什么“字符串字面量”不能当可靠锁
像 synchronized("order-1001") 这类写法,表面看是同一个字符串,但问题在于:
- 该字符串被所有模块共享:只要任意其他类也写了
"order-1001",就可能意外串入同一把锁,导致本不该互斥的逻辑被强同步,引发隐蔽阻塞 - 编译期字面量虽进常量池,但运行时拼接(如
"order-" + userId)或String.valueOf()生成的仍是新对象,根本进不了池——锁失效 - 即使强制用
.intern(),JDK7+ 后它操作的是堆内元空间常量池,高并发下易撑爆 Metaspace,且intern()方法本身是全局同步的,反而成性能瓶颈
真正安全的字符串相关加锁策略
如果业务必须按用户ID、订单号等字符串维度做并发控制(如秒杀防重、单用户限流),应绕过字符串锁,改用可控、隔离、可回收的锁对象:
- 用
ConcurrentHashMap<String, Object>缓存唯一锁对象:首次请求时computeIfAbsent(key, k -> new Object())创建,后续复用;注意定期清理避免内存泄漏 - 对锁粒度做分片:比如按字符串哈希值取模,映射到固定数量的预分配锁对象数组(如 64 个
new Object()),既避免全局竞争,又防止锁爆炸 - 关键场景优先交由外部组件保障:数据库行锁(
SELECT ... FOR UPDATE)、Redis Lua 脚本原子执行、或分布式锁(如 Redisson),比 JVM 层字符串锁更可靠
绝对要避开的写法
以下代码看似简洁,实则埋雷:
立即学习“Java免费学习笔记(深入)”;
-
synchronized(userId + "")—— 每次都是新 String 对象,锁完全无效 -
synchronized(String.valueOf(userId).intern())—— OOM 风险 + 全局同步点 -
synchronized("LOCK_" + bizType)—— 字面量拼接在编译期不成立,运行时仍是新对象 -
synchronized("global")或任意固定字面量 —— 多模块共用,锁范围失控
替代方案推荐顺序
按可靠性与适用性排序:
- 单机强一致性:ConcurrentHashMap + 分片 Object 锁 + 超时机制
- 需跨进程/服务:Redis Lua 脚本(如
SETNX+ 过期时间) - 复杂业务逻辑:数据库乐观锁(version 字段)或悲观行锁
- 简单计数/开关:AtomicInteger / AtomicBoolean,无需 synchronized


















