stringbuffer 的线程安全真实有效但性能代价高,因所有修改方法均使用 synchronized 粗粒度锁,导致单线程下比 stringbuilder 慢 50%–85%,仅在真正共享实例且无法规避竞争时才必要。

StringBuffer 的线程安全是真实有效的,但它的实现方式直接决定了性能代价——不是“有没有影响”,而是“影响有多大、值不值得承担”。
线程安全靠的是方法级 synchronized 锁
所有修改操作(append、insert、delete、reverse 等)都用 synchronized 修饰,锁对象是 this。这意味着:哪怕只是往缓冲区末尾加一个字符,整个 StringBuffer 实例都会被独占,其他线程必须排队等待。这种粗粒度锁能防止数据错乱,但无法支持并发读写或部分区域并发修改。
- 没有分段锁、没有 CAS 优化,纯靠重量级 monitor 控制
- toString() 虽有缓存(toStringCache),但缓存更新仍被同步块包裹,不提升并发效率
- 连 length() 这种只读方法都没加锁,但实际调用 append 时,锁已覆盖整个操作周期
性能损失在单线程下就很明显
实测表明:相同逻辑下,StringBuffer 比 StringBuilder 慢 50%–85%。例如 JDK 17 中 100 万次 append,典型耗时为 StringBuffer 120ms vs StringBuilder 65ms。这不是 JVM 优化不到位,而是设计层面的同步开销无法绕过:
- JVM monitor enter/exit 指令本身有成本
- 即使无竞争,JIT 也只能部分消除同步,不能完全抹平锁结构带来的指令序列限制
- 频繁方法调用叠加同步语义,影响 CPU 流水线效率
真正需要 StringBuffer 的场景其实非常有限
只有当多个线程**共享同一个 StringBuffer 实例**,且无法通过代码结构规避竞争时,才构成刚性需求:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 全局日志聚合器(如多线程共用一个 sb 收集日志后统一刷盘)
- 遗留系统中无法重构的跨线程字符串构建逻辑
- Servlet 容器内极少数需跨请求复用的配置组装器(通常应避免)
注意:如果每个线程都 new 自己的 StringBuffer,那它和 StringBuilder 行为一致,还多背一份同步包袱——这种用法毫无意义。
更合理的替代思路是“规避共享”而非“依赖同步”
95% 以上的字符串拼接发生在方法内部,天然线程隔离。优先采用:
- StringBuilder + 局部变量:最常用、最高效
- 拼完再 toString() 传递不可变 String:跨线程传递结果,而非共享可变对象
- 必要时用 ThreadLocal
:避免重复创建,又不牺牲线程安全性
面试中若被问及“为什么不用 StringBuffer”,重点不是否定它,而是展示你对锁成本的敏感度和对协作设计的理解——安全不是靠加锁堆出来的,而是靠减少共享做出来的。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










