Java多线程编程:StringBuffer为何慢于StringBuilder

作者:袖梨 2026-06-23
StringBuffer因所有公开方法加synchronized而比StringBuilder慢2–4倍,核心差异仅在于同步关键字,二者底层实现完全相同;线程安全仅在多线程共享同一实例时必要,局部使用应选StringBuilder,并预设容量以减少扩容开销。

因为 StringBuffer 的每个公开方法都加了 synchronized,而 StringBuilder 完全不加锁——同步机制本身就会带来锁获取、释放、内存屏障等开销,哪怕只有一个线程在跑,也绕不开。

核心差异就在一个关键字上

StringBuffer 和 StringBuilder 底层共享同一套实现(都继承自 AbstractStringBuilder),用的都是可变的 char[](JDK9+ 改为 byte[]),扩容逻辑、append 行为、内部缓冲管理全都一样。唯一的区别是:StringBuffer 的 append()toString()delete() 等所有公共方法签名上都明确写了 synchronized;StringBuilder 则完全没这层修饰。

这意味着:

  • 每次调用 StringBuffer 方法,JVM 都必须执行锁的进入与退出流程
  • 即使当前线程是唯一使用者,也要承担锁的内存可见性保障成本
  • JVM 对 synchronized 块会做更保守的优化,比如限制指令重排范围

性能差距不是“略慢”,而是实打实的开销

在单线程下做 10 万次 append("x")

立即学习“Java免费学习笔记(深入)”;

  • StringBuilder 通常耗时约 8–12ms
  • StringBuffer 往往需要 28–45ms,慢了约 2–4 倍

这个差距不来自算法或数据结构,纯粹来自同步带来的额外字节码指令和运行时机制。锁竞争越少,相对差距越稳定;但只要加了 synchronized,就不可能比无锁版本快。

线程安全不是默认选项,而是明确需求

StringBuffer 的线程安全是有代价的,但它只在一种场景下真正必要:

  • 多个线程**同时读写同一个 StringBuffer 实例**(例如作为 static 全局变量、被注入到 Spring 单例 Bean 中并跨请求复用)
  • 且你无法重构为线程局部使用(如 ThreadLocal<StringBuilder>)或不可变结果传递

如果只是方法内局部创建、拼完即弃,或者通过 ThreadLocal 隔离,用 StringBuffer 就等于主动给单线程任务加锁——白耗资源,还掩盖真实并发风险。

别忽略容量预设这个小优化

无论选哪个,如果能预估最终长度,初始化时指定容量能减少数组复制次数:

  • new StringBuilder(1024)new StringBuilder()(默认 16)更适合拼接长文本
  • 扩容公式是 新容量 = 旧容量 × 2 + 2,频繁扩容会触发多次 System.arraycopy

这点对两者都适用,但对 StringBuilder 更值得做——它本就轻量,省下的复制开销更直接体现为性能提升。

相关文章

精彩推荐