StringBuilder线程不安全,因其count += len非原子且扩容过程无同步保护:多线程并发时count丢失导致长度不足,竞态扩容引发ArrayIndexOutOfBoundsException;StringBuffer通过synchronized串行化所有方法,确保count更新与数组操作原子性。

直接模拟 StringBuilder 线程不安全,关键不是“写个并发程序跑一下”,而是要精准暴露它内部两个核心缺陷:非原子的 count += len 和竞态下的数组扩容。下面几种方式,面试时能讲清楚原理、能写出可复现的代码、还能说明为什么 StringBuffer 没这问题。
用多线程反复 append 导致长度丢失
这是最经典、最直观的复现方式。创建多个线程,共享同一个 StringBuilder 实例,各自循环追加固定字符:
- 启动 10 个线程,每个线程执行 1000 次
sb.append("x") - 预期总长度是 10000,但实际输出常小于该值(如 9326)
- 根本原因是
count += len不是原子操作:两个线程同时读到 count=10,各自加 1 后都写回 11,丢了一次计数
触发 ArrayIndexOutOfBoundsException 异常
比长度丢失更严重的是崩溃。这个异常不是必现,但只要并发足够高、扩容临界点被多线程同时击中,就可能抛出:
- StringBuilder 底层 char 数组容量不足时,会调用
ensureCapacityInternal扩容 - 扩容过程包含:新建更大数组 → 复制旧内容 → 赋值给
value字段 - 若线程 A 刚完成扩容赋值,线程 B 还在用旧数组地址执行
str.getChars(..., value, count),就会越界写入 - 所以运行时偶尔出现
ArrayIndexOutOfBoundsException,正是线程不安全的铁证
局部变量 vs 共享实例 —— 关键判断点
面试官常追问:“方法里 new 出来的 StringBuilder 就一定安全吗?”答案取决于作用域:
立即学习“Java免费学习笔记(深入)”;
- ✅ 安全场景:方法内创建、只在本线程栈帧中使用(如
void f(){ StringBuilder sb = new StringBuilder(); sb.append(...); }) - ❌ 不安全场景:作为参数传入、作为静态字段、作为返回值暴露出去、被多个线程显式共享
- 重点:线程安全与否,看的是对象是否被多个线程**同时访问并修改**,而不是类本身有没有 synchronized
对比 StringBuffer 验证同步机制
把上面任一测试换成 StringBuffer,结果立刻稳定:
- 所有方法(包括
append)都被synchronized修饰 - 同一时刻只有一个线程能进入方法体,
count += len和扩容逻辑都被串行化 - 运行后长度严格等于预期值,且从不抛 ArrayIndexOutOfBoundsException
- 这反向印证了 StringBuilder 的问题不在算法逻辑,而在缺少同步控制


















