StringBuffer的同步仅保障单方法原子性,不保护多步操作;其所有公开修改方法均用synchronized修饰,锁对象为this实例,确保同一实例的并发修改不会破坏内部状态,但“检查-再操作”需显式同步块。

是的,Java 中 StringBuffer 的方法同步在单个方法调用层面确实保证了线程安全,但这种安全有明确边界——它只覆盖单次方法执行的原子性,不自动延伸到多步操作或业务逻辑。
也就是说:
✅ 同一 StringBuffer 实例上并发调用 append()、delete()、reverse() 等公开修改方法,不会导致内部状态(如 count、char[] value)错乱或数组越界;
❌ 但像 if (sb.length() < 10) sb.append("x") 这类“先读再写”的组合操作,不被自动保护,中间可能被其他线程插入修改,引发竞态。
StringBuffer 的同步是怎么工作的
- 所有关键方法(
append、insert、delete、toString等)都声明为public synchronized; - 锁对象是
this(即当前StringBuffer实例),不是类锁,因此不同实例互不影响; - 每次方法进入时尝试获取该实例锁,执行完自动释放,异常时也保证解锁;
- 非同步方法(如
length()、charAt())虽可并发读取,但它们返回的是瞬时快照,不提供内存可见性保障(不过因同步方法会刷新主内存,实际影响有限)。
哪些情况真正依赖这个同步
只有当多个线程:
- 共享同一个
StringBuffer实例(例如作为static字段、或被多个Runnable引用); - 并且持续并发读写(不只是各自创建后拼接再丢弃);
- 且无法通过重构避免共享(比如改用
ThreadLocal<StringBuilder>或局部变量);
这时StringBuffer的同步才有实际意义。
常见误用与风险点
-
检查-然后-操作(check-then-act):
if (sb.length() == 0) { // 可能刚判断完就被别的线程 append 了 sb.append("init"); }正确做法是加显式同步块:
synchronized (sb) { if (sb.length() == 0) { sb.append("init"); } } -
toString() 的缓存行为:
toString()有toStringCache字段优化,但缓存更新仍受同步保护,不额外提升并发效率; -
性能代价真实存在:
单线程下比StringBuilder慢约 50%–85%,高并发时锁竞争更明显,吞吐量受限。
更优替代思路(多数场景适用)
- 局部使用 → 直接选
StringBuilder; - 多线程需拼接 → 用
ThreadLocal<StringBuilder>,零锁、高性能; - 需聚合结果 → 各线程生成片段后,由单一线程合并(如用
ConcurrentLinkedQueue收集); - 构建响应体等临时场景 → 尽量避免共享可变对象,从设计上消除竞争。
不复杂但容易忽略。
立即学习“Java免费学习笔记(深入)”;


















