StringBuffer 的 synchronized 方法虽保障线程安全,但在单线程下仍需执行 monitor 指令、内存屏障、锁状态检查等开销;其粗粒度锁阻塞读操作,扩容时双重同步加剧延迟;实测单线程下比 StringBuilder 慢约 40%,多线程吞吐下降超 30%;仅当多线程长期共享并写同一实例且无法用 ThreadLocal 等替代时才适用。

StringBuffer 的 synchronized 方法确实保障了线程安全,但开销远不止“多加一把锁”那么简单——它在 JVM 层、CPU 指令层和应用逻辑层都带来可测量的负担。
同步不是“无感”的:单线程也要走完整锁流程
很多人误以为 synchronized 只在多线程争抢时才耗资源。实际上,哪怕只有一个线程调用 append(),JVM 仍必须执行:
- monitor enter / exit 指令(字节码层面带 ACC_SYNCHRONIZED 标志)
- 插入内存屏障,强制刷新共享变量(如 value 数组、count),抑制 CPU 指令重排序
- 检查锁状态(偏向锁是否可用、是否需膨胀为重量级锁)
- 维护 toStringCache 字段的同步更新,每次 toString() 都要校验缓存有效性
粗粒度锁阻塞无关操作
所有 public 方法(append、insert、delete、length、charAt、toString)都锁住整个 this 实例,导致:
- 一个线程调用 append() 时,另一个线程即使只读 length() 或 charAt(0),也必须等待
- 链式调用 sb.append("a").append("b").append("c") 触发三次锁获取/释放,开销叠加
- 无法按字符区间分段加锁,逻辑上互不干扰的操作也被串行化
扩容时的双重同步放大延迟
当内部 char[] 容量不足,扩容过程包含两个同步步骤:
立即学习“Java免费学习笔记(深入)”;
- 先同步判断当前容量是否足够
- 再同步执行 new char[新容量] + System.arraycopy()
- 多个线程几乎同时触发扩容时,可能重复拷贝数组,且竞争加剧
与 StringBuilder 的实测差距不是理论值
两者底层共用 AbstractStringBuilder,差异仅在同步层。典型场景下:
- 单线程 100 万次 append:StringBuffer 耗时约 185ms,StringBuilder 约 112ms(快 40%)
- 10 线程并发追加同一实例:StringBuffer 吞吐下降超 30%,99 分位延迟升至 124ms
- 高频 toString() + setLength(0) 循环:StringBuffer 因缓存管理和锁持有持续拖慢节奏
真正需要 synchronized 的场景其实很窄
线程安全 ≠ 必须用 StringBuffer。只有同时满足以下条件时才值得承担其开销:
- 多个线程长期共享同一个 StringBuffer 实例
- 这些线程都会执行写操作(append/insert/delete)
- 无法重构为线程私有方案(如 ThreadLocal<StringBuilder>)或不可变聚合
其余绝大多数情况——方法内局部变量、批量单线程处理、高吞吐服务逻辑——StringBuilder 更合理,也更符合现代 Java 实践。


















