stringbuffer 的线程安全依赖方法级 synchronized,但带来显著性能开销:单线程仍执行 monitor 指令、内存屏障及锁状态判断;粗粒度锁阻塞无关操作;tostringcache 增加同步负担;相比 stringbuilder,单线程快 10%–15%,低竞争多线程可达 5 倍差距;选型应基于真实共享与竞争模式,而非仅线程数量。

StringBuffer 的线程安全靠的是方法级 synchronized,但它不是“免费的午餐”——锁带来安全,也拖慢速度。单线程下它照样要走完整锁流程,而多线程低竞争时,开销反而更显眼。
同步锁的真实成本不止是“排队”
很多人以为 synchronized 只在多线程争抢时才有代价,其实不然:
- JVM 每次调用 synchronized 方法,都要执行 monitor enter/exit,哪怕当前只有 1 个线程
- 必须插入内存屏障(memory barrier),保证字段修改对其他线程可见——这会抑制指令重排序,影响 CPU 流水线效率
- 即使没竞争,偏向锁也可能被撤销、轻量级锁可能膨胀为重量级锁,这些判断逻辑照常运行
- toString() 还要维护 toStringCache,而这个缓存本身也要被同步保护,进一步增加路径复杂度
粗粒度锁限制了并发潜力
StringBuffer 几乎所有 public 方法(append、insert、delete、reverse、length、toString)都加了 synchronized,且锁对象是 this:
- 一次 append 就得独占整个 StringBuffer 实例,哪怕另一个线程只想读 length 或调用不冲突的 charAt,也得等
- 无法按需锁定内部 char[] 的某一段,比如只锁写入区域,而是“全有或全无”
- 链式调用如 sb.append("a").append("b").append("c") 会反复进出锁,三次 monitor 操作叠加开销
与 StringBuilder 的差距不只是“有没有锁”
两者共用 AbstractStringBuilder 底层,差异集中在同步层:
- StringBuilder 所有方法都是普通 invokevirtual,无 monitor 指令,无锁检查,无缓存一致性维护
- toString() 直接复制数组,看似多一次 copy,实则省去了锁+缓存校验的综合成本
- 实测显示:单线程高频拼接场景,StringBuilder 通常快 10%–15%;高并发但无实际竞争时,性能差距可达 5 倍
选型不能只看“是否多线程”,要看真实竞争模式
线程安全 ≠ 必须用 StringBuffer:
- 若 StringBuffer 实例仅被单一线程持有(如局部变量、RequestScope Bean),用 StringBuilder 更合理
- 若多个线程共享同一实例,且存在写操作竞争,才真正需要 StringBuffer 的同步保障
- 若需更高并发吞吐,可考虑无锁方案(如 ThreadLocal
)或更现代的并发容器(如 ConcurrentLinkedQueue 配合字符缓冲)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











