StringBuffer 线程安全靠每个公开方法加 synchronized 锁住 this 实例,保障单方法原子性、可见性与有序性;但锁粒度粗、读写均阻塞,4 线程并发性能约为 StringBuilder 的 1/2.8,高频共享场景应优先考虑 ThreadLocal 等无锁方案。

Java 中的 StringBuffer 本身就是线程安全的,不需要“再实现”——它的线程安全性是 JDK 原生提供的,靠的是每个公开方法(append、insert、delete、toString 等)都用 synchronized 修饰,锁对象是实例本身(this)。也就是说,它天然支持多线程并发读写同一个实例,不会出现数组越界、内容覆盖或 count 错乱等问题。
高并发下锁竞争到底有多强
竞争强度不取决于线程数量,而取决于三个实际因素:同一实例被调用的频率、每次操作耗时、以及是否频繁触发扩容。实测中常见表现如下:
- 4 个线程并发各执行 10 万次
append("x")(短字符串),总耗时约为单线程StringBuilder的 2.8 倍 - 若每次拼接的是长字符串(如 1KB 文本),锁持有时间拉长,线程排队加剧,吞吐量可能下降 5 倍以上
- 即使只有 2 个线程,只要都在高频调用
sb.append()和sb.toString(),也会明显观察到线程阻塞(例如jstack显示大量WAITING on java.lang.StringBuffer@xxx) - JVM 虽有偏向锁、轻量级锁等优化,但在真实高竞争场景下,锁常会膨胀为重量级锁,带来上下文切换和内存屏障开销
为什么看起来“只是加个 synchronized”却这么慢
因为 StringBuffer 的锁是粗粒度的,且覆盖全部公共方法:
-
length()、charAt()、toString()全部同步——读操作也要排队,哪怕和写操作逻辑上不冲突 - 链式调用如
sb.append("a").append("b").append("c")会进出锁三次,每次都有monitorenter/exit指令开销 - 扩容(
ensureCapacityInternal)也包裹在同步块内,多个线程不会同时 realloc,但会轮流等待 -
toString()还要维护toStringCache字段,该缓存的读写同样受同步保护,增加路径复杂度
别只盯着“能不能用”,先问“要不要共享”
绝大多数高并发场景其实并不需要真正共享一个 StringBuffer 实例。更合理的选择往往更轻量、更可控:
立即学习“Java免费学习笔记(深入)”;
- 单线程拼接 → 直接用
StringBuilder(快 10%–15%,无锁开销) - 每个线程独立拼接 →
ThreadLocal<stringbuilder></stringbuilder>(零竞争,性能接近纯单线程) - 需合并结果(如日志聚合)→ 各线程用本地
StringBuilder拼完,最后由主线程或专用收集线程统一处理 - 超高频、低延迟聚合(如实时指标统计)→ 改用无锁队列(如
Disruptor)或分段计数器,绕过字符串拼接瓶颈
StringBuffer 的线程安全是真实的,但它的代价也是实在的。与其花精力压测和调优它的锁竞争,不如从设计上减少共享——这通常比“怎么让 StringBuffer 更快”更能解决问题。


















