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

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

















