stringbuffer 仅在多线程共写同一实例且无法重构为独立拼接时适用,需预设合理初始容量、手动同步组合逻辑,并避免滥用 tostring()。

直接用 StringBuffer 替代 String 进行频繁修改,在多线程环境下能解决“不可变导致频繁对象创建”的问题,但关键不是“替换了就行”,而是“怎么共享、怎么操作、怎么避坑”。StringBuffer 本身不提升性能,只保障单次修改不乱;用错方式,照样出错或拖垮系统。
明确适用前提:真需要多个线程共写同一个缓冲区
不是所有多线程场景都适合用 StringBuffer。它只在以下情况真正必要:
- 多个线程持续向同一个实例追加日志、拼接响应体、组装配置参数
- 无法重构为各线程独立拼接再合并(如遗留系统强耦合)
- 线程数较少(通常 ≤4),且对吞吐量要求不高,更看重结果确定性
反例:Controller 方法里 new 一个 StringBuffer,再传给几个子线程——此时每个线程该用自己的 StringBuilder 或 ThreadLocal
初始化容量必须预估并显式设置
默认初始容量是 16,一旦超出就会触发扩容(new char[old * 2 + 2])。扩容涉及数组复制,而这个过程也被 synchronized 锁保护,极易成为锁竞争热点。
建议根据典型使用长度设合理初始值:
- 日志行拼接 → new StringBuffer(512)
- HTTP 响应头聚合 → new StringBuffer(256)
- SQL 参数构建 → 按最大预期字段数估算后设值
容量设得略大无害,设小了却会反复锁住整个实例做复制,性能断崖式下降。
组合逻辑必须手动加锁,不能依赖方法级同步
StringBuffer 的 append()、delete() 等方法是 synchronized,但 length() 是非同步的普通方法。像下面这种写法极危险:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
if (buf.length()
中间存在竞态窗口:A 线程读到 length=8,还没 append,B 线程已追加 3 字符,A 再 append 就可能超长或逻辑错乱。
正确做法是把检查和修改包裹进同一把锁:
✅ 正确写法synchronized (buf) { if (buf.length()
避免 toString() 后继续误用,别把它当长期共享容器
toString() 返回的是新 String 对象,它和 StringBuffer 实例完全解耦。后续对该 String 的任何操作(比如再拼接、截取)都不受 StringBuffer 锁保护,也不会反馈回原缓冲区。
更要注意:JDK 9+ 中 toString() 使用内部缓存(toStringCache),高并发下调用可能失效,反而增加无效计算。因此:
- 只在真正需要最终字符串结果时调用一次 toString()
- 不要在循环中反复调用 toString() 获取中间状态
- 若需分段处理,优先用 List
收集片段,最后用 String.join() 合并
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










