stringbuilder多线程共享调用append()必然数据错乱,因扩容、写入、更新count三步均无锁保护,导致数组重复新建、内容覆盖、长度丢失及越界异常;而stringbuffer通过synchronized保证三步串行执行,状态一致。

StringBuilder 本身不加锁,多线程共享同一个实例调用 append() 时,会因内部状态竞争直接引发数据错乱——不是偶尔出错,而是机制性缺陷。
核心问题在三步操作完全裸奔
看 AbstractStringBuilder.append() 的关键逻辑:
-
检查扩容:
ensureCapacityInternal(count + len)—— 多个线程可能同时判断“容量不够”,各自触发扩容,导致数组被重复新建、旧引用未及时失效 -
写入字符:
str.getChars(0, len, value, count)—— 若两个线程读到相同的count值,就会往同一段value数组位置写入,内容互相覆盖(比如本该是"AB",结果变成"BB"或"AA") -
更新长度:
count += len—— 非原子操作:线程 A 和 B 同时读count=5,各自加 1 后都写回6,实际应为7,造成后续写入位置偏移或越界
典型错乱表现不止是长度不准
运行两个线程分别追加 "A" 和 "B" 各 1000 次,结果可能:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 总长度 小于 2000(
count更新丢失) - 字符串中出现 AB 交错但顺序混乱,如
"AABBAAB..."中夹杂重复或跳空 - 偶发 ArrayIndexOutOfBoundsException:一个线程刚扩容完并更新了
value引用,另一个线程仍按旧数组长度和旧count写入,索引超出旧数组边界 - 极少数情况下拼出 null 字符或乱码(底层
char[]被部分覆盖或未初始化区域被读取)
错乱是否发生,只取决于是否共享实例
不是“并发高才出问题”,而是只要多个线程同时修改同一个对象,风险就存在:
- ✅ 安全:方法内
new StringBuilder(),仅当前线程使用,哪怕在线程池中也无影响 - ❌ 危险:声明为
static字段、Spring Bean 成员变量、作为参数传入公共工具方法、放进缓存或静态 Map 中复用 - ⚠️ 隐蔽陷阱:日志工具类里持有一个
StringBuilder实例反复setLength(0)清空重用——若该工具类是单例,就等于把错乱风险打包分发给了所有调用方
为什么 StringBuffer 不会这样
它的 append() 方法签名是 public synchronized StringBuilder append(String str)。整个方法体被一把锁包住,意味着:
- 扩容、写入、更新
count这三步一定串行执行 - 任一线程进入方法时,看到的
value和count都是最新的、一致的状态 - 不会出现两个线程同时读
count、同时写value的竞态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










