stringbuffer 通过 synchronized 方法实现线程安全,以实例自身为锁对象,粒度为整个对象,保证单个方法原子性但不保证复合操作原子性;性能瓶颈源于锁竞争而非同步本身,逃逸分析可能消除局部变量的锁。

StringBuffer 通过在所有修改状态的方法上加 synchronized 实现内部锁机制,从而防止多线程环境下的数据竞争。
锁对象是 StringBuffer 实例本身
每个 StringBuffer 对象都以自身为锁目标。调用 append()、insert()、delete() 等方法时,线程必须先获取该实例的 intrinsic lock(即 monitor 锁),其他线程在此期间无法进入任何 synchronized 方法。
- 锁粒度是整个对象,不是字段或某段逻辑——只要一个线程在执行 append(),另一个线程即使只想调用 length() 或 deleteCharAt(),也得等待
- 这意味着所有共享状态(如 value 数组、count 字段)的读写都被串行化,消除了“读-改-写”类竞态,比如多个线程同时 append 不会导致数组越界或 count 错乱
同步覆盖全部关键操作,但不保证复合操作原子性
单个方法是线程安全的,但多个方法组合调用仍可能出问题。
- 例如:
if (sb.length() == 0) sb.append("default");中,length() 和 append() 是两个独立同步方法,中间存在时间窗口:另一线程可能在这之间已向 sb 添加内容,导致重复追加 - 这种“检查后使用”(check-then-act)属于典型竞态,需外部加锁:
synchronized(sb) { if (sb.length() == 0) sb.append("default"); }
锁竞争是性能瓶颈的根源,而非同步逻辑本身
真正拖慢 StringBuffer 的不是 synchronized 关键字,而是多线程争抢同一把锁带来的阻塞和上下文切换开销。
- 单线程下,StringBuffer 比 StringBuilder 慢 80% 以上(100 万次 append 实测),这纯粹来自同步方法调用的额外指令与锁入口开销
- 多线程下,若多个线程频繁调用同一 StringBuffer 的 append(),会形成明显锁争用,吞吐量急剧下降;而若各线程操作不同 StringBuffer 实例,则无竞争——因为锁对象互不相同
JVM 会尝试优化局部 StringBuffer 的锁开销
逃逸分析 + 锁消除可在特定场景下移除同步,让性能接近 StringBuilder。
- 条件苛刻:StringBuffer 必须是方法内局部变量、未逃逸(没被返回、没存入静态/成员变量、没传给 native 方法等)
- 示例中
StringBuffer sb = new StringBuffer(); sb.append("a").append("b");很可能被 JIT 编译器消除锁,但不能依赖——它取决于运行时 profile 和编译层级 - 锁粗化也可能发生:循环内连续调用 append() 可能被合并为一次大范围加锁,减少加锁次数,但会延长单次持锁时间
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











