stringbuffer 的线程安全依赖每个公开方法加 synchronized 实现粗粒度锁,性能开销大且适用场景有限;仅当多线程共享同一实例并并发修改时才需使用,否则应优先选用 stringbuilder。

StringBuffer 的线程安全不是“开箱即用”的万能保障,而是靠每个公开方法加 synchronized 实现的粗粒度锁,代价明确、适用场景有限。它只在真正共享且并发修改同一实例时才值得用;绝大多数情况下,用 StringBuilder 更合理、更快、更干净。
同步是怎么工作的
StringBuffer 所有可变操作(append、insert、delete、reverse 等)和部分只读方法(如 length()、toString())都声明为 synchronized,锁对象是实例本身(this)。JVM 在调用时自动执行 monitor enter/exit 指令,确保同一时刻只有一个线程能进入这些方法。
- 底层字节码带
ACC_SYNCHRONIZED标志,由 JVM 直接支持,无需手写synchronized块 - 内部字段
count和value[]的读写被同步保护,避免数据撕裂 -
toString()也同步,并维护toStringCache缓存,但返回的是新String,后续对该String的操作不再受锁约束
性能开销从哪来
同步不是“多线程争抢时才有成本”,单线程下照样走完整锁流程:monitor 指令、内存屏障、锁状态判断、缓存一致性维护——这些都会抑制 CPU 流水线效率。
- 即使无竞争,偏向锁可能被撤销,轻量级锁可能膨胀,判断逻辑照常运行
- 链式调用如
sb.append("a").append("b")会反复进出锁,三次 monitor 开销叠加 - 一次
append就锁住整个对象,其他线程想调用length()或charAt()也得排队,无法并发读 - 实测显示:单线程百万次拼接,StringBuffer 比 StringBuilder 慢约 80%–100%;4 线程低竞争场景,差距可达 5 倍
哪些场景真需要 StringBuffer
它的线程安全性只在满足全部三个条件时才产生实际价值:多个线程共享同一个实例 + 并发调用修改方法 + 无法通过重构规避共享。
- 全局日志缓冲器:多个线程共用一个
StringBuffer收集日志,再批量刷盘 - 遗留系统中跨线程传递并反复修改的配置构建器,且重构成本过高
- 轻量级并发拼接(如 2–4 线程、QPS 很低),追求简单而非吞吐量
- 反例:在
Runnable.run()中新建StringBuffer仅本线程使用——此时用StringBuilder更合适
常见误区与替代思路
方法级同步 ≠ 业务逻辑安全。像 if (sb.length() 这类 check-then-act 操作,两步之间存在竞态窗口,必须显式用 <code>synchronized(sb) { ... } 包裹整块逻辑。
- 优先考虑线程隔离:用
ThreadLocal<stringbuilder></stringbuilder>,每个线程独享,零锁开销 - 改用不可变方案:如
String.join()、Collectors.joining(),适合短串或低频拼接 - 异步聚合场景:用
ConcurrentLinkedQueue收集片段,最后单线程合并 - 预分配容量:
new StringBuilder(1024)避免扩容拷贝,提升局部拼接效率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











