stringbuilder不是线程安全的,多线程共享调用append()等方法会导致字符丢失、数据错乱或arrayindexoutofboundsexception,因其内部char数组扩容和count更新缺乏同步。

StringBuilder 本身不是线程安全的,多线程环境下直接共享并并发调用其 append()、toString() 等方法,确实可能引发字符丢失、数据错乱,甚至 ArrayIndexOutOfBoundsException(AIOOBE)。这不是“偶发 bug”,而是明确的未定义行为——根本原因在于内部 char 数组扩容和指针更新缺乏同步。
为什么会出现 ArrayIndexOutOfBoundsException?
StringBuilder 底层维护一个 char 数组和一个 count 记录当前长度。当多个线程同时执行 append():
- 线程 A 检查容量足够,准备写入位置
count; - 线程 B 同时也检查并写入同一位置,还把
count加了 1; - 线程 A 接着按旧的
count值写入,可能越界(比如数组刚被 B 扩容但 A 还在用旧引用),或覆盖 B 的数据; - 更糟的是,B 在扩容后更新了数组引用和
count,而 A 仍操作旧数组,导致索引超出旧数组长度 → AIOOBE。
如何快速定位是否是 StringBuilder 并发问题?
观察异常堆栈和业务场景:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 异常出现在
StringBuilder.append()、StringBuilder.toString()或StringBuffer.ensureCapacity()(注意:如果误用了 StringBuffer 却没加锁,也可能因其他逻辑出错); - 该 StringBuilder 实例是类成员变量、静态变量,或被多个线程通过参数/闭包共享;
- 复现不稳定,但高并发压测时频率显著上升;
- 日志中发现拼接结果缺失、顺序错乱、出现 null 字符或乱码(非编码问题)。
排查与验证的实用方法
不用等线上炸锅,本地就能验证:
- 加日志打点:在 append 前后打印线程名 + 当前 count + capacity,看多个线程是否交替修改同一实例;
- 用 JOL(Java Object Layout)观察内存布局:确认你操作的确实是同一个对象(而非每次 new);
- 最小复现代码:写个 2~3 线程循环 append 字符串,跑几万次,大概率复现 AIOOBE;
-
Arthas trace:在线上用
trace java.lang.StringBuilder append查看调用链路,确认是否跨线程共享。
正确解法:别修 StringBuilder,换用合适工具
不推荐给 StringBuilder 加 synchronized(破坏封装且易漏),应从设计上规避:
- 局部变量优先:方法内 new StringBuilder,用完丢弃 —— 最安全、最高效;
-
ThreadLocal 缓存:若创建开销敏感(如高频日志拼接),用
ThreadLocal<stringbuilder></stringbuilder>; - 并发场景改用 StringJoiner 或 List + Collectors.joining():适合拼接已知集合;
- 必须共享?用 StringBuffer(仅当 legacy 要求)或显式同步块:但需确保所有访问路径都被锁覆盖,否则仍危险。
本质上,这不是 StringBuilder 的缺陷,而是把它当成了线程安全容器来用。明确它的定位:高性能单线程字符串构建器。用对地方,问题自然消失。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










