StringBuilder 比 StringBuffer 显著更快,因其无同步开销;StringBuffer 所有修改方法均加 synchronized,即使单线程也需执行锁机制与内存屏障,JDK17 下10万次append测试显示性能差距达2.3倍。

因为 StringBuilder 完全不加锁,而 StringBuffer 的每个修改方法都强制走 synchronized 同步流程——哪怕当前只有单线程在跑,也得完成锁的获取、内存屏障刷新、锁释放这一整套动作。
同步开销是性能差距的根源
StringBuffer 的 append、insert、delete、reverse 等所有 public 修改方法都用 synchronized 修饰。JVM 必须为每次调用执行锁机制:检查锁状态、可能触发线程调度、插入内存屏障确保可见性。这些操作本身不做事,却消耗 CPU 指令周期和缓存一致性资源。
StringBuilder 复用同一套底层实现(AbstractStringBuilder),只是去掉了 synchronized 关键字。执行路径更短,指令更少,没有锁相关的分支判断和状态维护。
实测差距明显但常被误判
在 JDK 17 下做 10 万次 append 测试:
立即学习“Java免费学习笔记(深入)”;
- StringBuilder 平均耗时约 12ms
- StringBuffer 平均耗时约 28ms
- 性能差距达 2.3 倍,不是“略快”,而是显著更快
这个差距与扩容、内存分配无关——两者的扩容策略完全一致(旧容量 × 2 + 2),差异纯属同步控制成本。
无竞争≠无开销
有人觉得“就一个线程,锁不会争抢,应该没影响”。这是误区。synchronized 在无竞争场景下虽不阻塞,但仍需执行轻量级锁的入口/出口逻辑,包括栈帧记录、偏向锁撤销(若启用)、内存屏障等。这些开销无法跳过,且随调用频次线性累积。
别让线程安全拖慢单线程代码
线程安全不是免费的,默认也不该是首选。如果变量是局部的、方法内创建、生命周期只限于当前线程(比如循环拼接、JSON 构建、SQL 拼装),用 StringBuffer 就等于主动给代码加了一层不必要的同步负担。
真正需要 StringBuffer 的场景很明确:多个线程共享同一个实例,并且会并发调用它的修改方法——这种设计本身已属少数,多数时候可通过 ThreadLocal、不可变结果返回或重构为无共享来规避。


















