stringbuffer线程安全但性能开销大,因方法加synchronized锁;stringbuilder无锁、单线程更快;多线程共享时应避免直接使用stringbuffer,优先用threadlocal或不可变设计。

StringBuffer 是线程安全的,StringBuilder 不是;同步开销主要来自 StringBuffer 内部方法的 synchronized 关键字,实际性能差异在高并发写入场景才明显。
同步机制如何体现
StringBuffer 的大多数修改方法(如 append()、insert()、reverse())都加了 synchronized 修饰符,意味着每次调用都会获取当前对象锁。而 StringBuilder 完全不加锁,所有操作直接操作内部 char 数组。
- 单线程下,synchronized 仍需执行锁检查(即使无竞争),带来轻微指令开销
- 多线程争用同一 StringBuffer 实例时,线程会阻塞等待,吞吐量下降明显
- 锁粒度是整个对象,不是某段字符区域——哪怕两个线程往不同位置 append,也得排队
典型场景下的开销对比
在局部变量、方法内临时拼接字符串时,两者性能差距极小,JVM 甚至可能对 StringBuffer 的单线程调用做锁消除(Lock Elision)优化。但以下情况差异显著:
- 多个线程频繁调用同一个 StringBuffer 的 append()(如日志聚合器共享缓冲区)
- 循环中反复调用 StringBuffer.toString() + 清空(toString() 创建新 String,但内部数组未重置,锁仍被持有)
- 将 StringBuffer 作为静态字段或长生命周期 Bean 的成员变量供多线程共用
怎么选?看使用上下文
判断依据不是“要不要线程安全”,而是“谁在访问、何时访问、是否真共享”:
- 局部变量(方法内新建、用完丢弃)→ 一律用 StringBuilder,安全且更快
- 需要跨线程传递并修改的可变字符串容器 → 先考虑是否必须可变;若必须,优先用 ThreadLocal
或每次创建新实例,而非共享 StringBuffer - 已有代码用 StringBuffer 且运行稳定 → 不必强行替换;但新增代码避免默认选它
- 真需要线程安全的字符串构建逻辑(极少)→ 可封装为不可变结果(如构建完转 String),或改用显式同步 + StringBuilder,更可控
验证开销的小技巧
别只看微基准测试(如 JMH 测单个 append),要模拟真实争用:
- 用 4~8 个线程并发调用同一 StringBuffer 的 1000 次 append,对比 StringBuilder(每个线程独享)的总耗时
- 观察 CPU 花费:StringBuffer 场景下,线程状态常为 BLOCKED,jstack 可见锁等待栈帧
- 开启 JVM 参数 -XX:+PrintGCDetails -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(高级)可进一步分析锁膨胀与编译优化行为










