StringBuilder与StringBuffer的核心区别是线程安全性与性能:StringBuffer所有修改方法加synchronized,保障多线程安全但有同步开销;StringBuilder无锁,单线程下快10%–20%,多线程共享时可能数据错乱或抛ArrayIndexOutOfBoundsException。

StringBuilder 和 StringBuffer 的核心区别就两点:线程安全性和性能。StringBuffer 所有修改方法(如 append、insert、delete)都加了 synchronized,同一时间只允许一个线程执行,所以多线程共享操作时不会出错;StringBuilder 完全没加锁,多个线程同时调用它的方法,就可能读写冲突、数据丢失,甚至抛出 ArrayIndexOutOfBoundsException。
底层共享状态没保护
两者都继承自 AbstractStringBuilder,共用两个关键字段:
- char[] value:实际存字符的数组
- int count:当前已用长度(即有效字符数)
比如 append("a") 的逻辑是:
① 检查容量是否够(ensureCapacityInternal)
② 把字符拷贝进 value 数组的 count 位置
③ 执行 count += len
问题就出在第③步——count += len 不是原子操作。它实际分三步:读 count → 加 len → 写回 count。两个线程同时读到 count=10、len=1,各自算完都写回 11,结果本该变成 12,却只变成 11。这就是最终长度比预期少的根本原因。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
扩容过程也可能崩
当容量不够时,ensureCapacityInternal 会触发扩容:新建更大数组 → 复制旧内容 → 替换 value 引用。
这个过程本身不是原子的。线程 A 刚完成扩容、还没来得及更新 count,线程 B 就去读 count 并用它当偏移量往 value 里写——但此时 value 已是新数组,而 count 还是旧值,写的位置就可能越界,直接抛 ArrayIndexOutOfBoundsException。
StringBuffer 怎么解决这些问题
它把整个 append 方法用 synchronized 包起来,相当于给整个操作上了“独占锁”:
- 任意时刻最多一个线程能进入方法体
- 所有对 value 和 count 的读写都在临界区内完成
- 天然避免了竞态条件和中间态暴露
代价是:高并发下线程要排队等待锁,吞吐量下降,响应延迟升高。
怎么选才合适
不用死记“谁快谁慢”,看场景:
- 单线程拼接(比如 for 循环组装 SQL、日志消息)→ 用 StringBuilder,没锁开销,最快
- 多线程共享同一个对象做拼接(比如全局缓存 builder)→ 必须用 StringBuffer,否则必出错
- 更现代的做法:避免共享可变对象。每个线程自己 new 一个 StringBuilder,最后用 Collectors.joining() 或并行流聚合,既安全又高效

















