StringBuffer成员变量在并发环境下看似安全实则存在风险,因其单方法调用虽线程安全,但多步操作(如判断后追加)非原子,易引发竞态;toString()返回的String不受锁保护;仅当多线程长期共享并共同修改时才适合作为成员变量,且关键复合操作须显式同步。

StringBuffer 作为成员变量在并发环境下能用,但必须满足“共享且多线程修改”这一前提,且需警惕单方法安全、多步操作不安全的典型陷阱。
为什么 StringBuffer 成员变量“看似安全”但实际有风险
StringBuffer 所有修改方法(如 append、delete、insert)都加了 synchronized,锁住整个实例。这意味着:一个线程调用 buffer.append("a") 时,其他线程无法同时执行 buffer.append("b") 或 buffer.reverse() —— 单次方法调用不会数据错乱。
但问题出在复合逻辑上。例如判断再追加:
-
if (buffer.length() == 0) buffer.append("init");不是原子操作:length() 返回后、append() 执行前,另一线程可能已往 buffer 写入内容,导致重复初始化或漏处理。 - toString() 返回的是新 String 对象,其内容不再受 StringBuffer 锁保护;后续对该 String 的任何操作(比如传给其他线程解析)与 StringBuffer 无关。
什么情况下才该把它设为成员变量
只有当这个 StringBuffer 实例被多个线程**长期共享并共同修改**时,才需要它作为类的成员变量。典型场景包括:
立即学习“Java免费学习笔记(深入)”;
- 日志聚合器:多个工作线程向同一个 StringBuffer 实例追加日志行,最后由主线程统一输出。
- 老系统兼容:已有代码将 StringBuffer 作为静态工具字段供全局使用,且无法重构为无状态设计。
- 缓存拼接器:作为某个服务类的私有 final 字段,被该类多个同步方法共用(此时还需确保这些方法自身也同步或协调访问)。
怎么用才真正安全
不能只依赖 StringBuffer 自带的 synchronized 方法。关键操作涉及多步判断或读-改-写时,必须显式加锁:
- 用 synchronized 块包裹整个逻辑:
synchronized (buffer) { if (buffer.length() - 若该 StringBuffer 是类的成员变量,也可直接同步 this(前提是该对象本身是共享且唯一协调点):
synchronized (this) { ... } - 避免在锁外暴露引用:不要把 buffer 通过 getter 暴露出去,防止外部绕过同步直接调用方法。
更推荐的替代思路
多数时候,用 StringBuffer 成员变量反而是过度设计。可考虑更轻量、更可控的方式:
- 用 ThreadLocal
:每个线程独享 StringBuilder 实例,彻底规避锁竞争,适合日志缓冲、模板渲染等场景。 - 用不可变+并发容器:各线程生成字符串片段,存入 ConcurrentLinkedQueue 或 CopyOnWriteArrayList,最后由单一线程合并。
- 局部创建 StringBuilder:如果只是在某个方法内拼接(如构造 SQL、JSON),直接 new StringBuilder(),性能高且无风险。


















