StringBuffer虽线程安全但性能差,因其所有方法均同步整个对象;高并发下应优先选用StringBuilder、ThreadLocal<StringBuilder>或ConcurrentLinkedQueue+StringBuilder合并等替代方案。

StringBuffer 在并发场景中确实线程安全,但它的安全是以显著性能损耗为代价的——不是它“不能用”,而是用错场景会直接拖慢系统吞吐。
为什么 StringBuffer 会成为并发瓶颈
它的所有修改方法(append、insert、delete、reverse 等)都加了 synchronized,锁的是整个对象实例。这意味着:
- 多个线程操作同一个 StringBuffer 实例时,必须串行执行,无法并行
- 哪怕两个线程分别往开头 insert 和末尾 append,也因锁粒度太粗而互相阻塞
- 扩容时(char[] 重新分配)会触发双重同步:先检查容量,再复制数组,竞争更剧烈
- toString() 方法也同步,频繁调用会放大锁开销,且每次返回新 String 对象,无法复用
高并发下更合理的替代方案
多数所谓“需要线程安全拼接”的需求,其实真正需要的是结果正确 + 高吞吐,而非共享一个可变缓冲区。推荐按场景选型:
- 单线程内多次拼接(如构建 SQL、生成 JSON)→ 直接用 StringBuilder,无锁、JVM 优化成熟
- 多线程各自拼接、最后合并(如日志格式化、批处理汇总)→ 使用 ThreadLocal<StringBuilder>,每个线程独享实例,零竞争
- 需跨线程累积字符串片段(如异步任务收集响应)→ 用 ConcurrentLinkedQueue<String> 收集,最终由单线程用 StringBuilder 合并
- 已知最终长度 → 构造时指定容量:new StringBuilder(2048),避免扩容抖动
如果非用 StringBuffer 不可,怎么减轻影响
它仍有存在价值,但只适用于低频、强共享、难重构的场景(如遗留配置中心的模板拼接器)。此时应:
立即学习“Java免费学习笔记(深入)”;
- 初始化时预估容量:new StringBuffer(1024),减少扩容次数和同步争抢
- 避免在循环里反复调用 toString(),把转换延迟到拼接完成之后
- 不要把它声明为 static 全局变量暴露给大量线程;封装在有明确生命周期的工具类中,用完即弃
- 复合逻辑(如“先判断长度再追加”)必须显式同步整个代码块,仅靠方法级 synchronized 不足以保证业务原子性
一句话总结选型逻辑
优先让拼接行为线程隔离,而不是让缓冲区线程共享;能用不可变聚合 + 最终合并,就别硬扛 synchronized;只有当多个线程必须共写同一可变缓冲区、且并发强度极低时,StringBuffer 才是合理选择。



















