StringBuffer线程安全因其所有公共方法均加synchronized锁,确保多线程日志拼接不丢失、不交叉且toString()返回一致快照;推荐按需创建而非共享实例以避免性能瓶颈和OOM。

StringBuffer 本身是线程安全的,可以直接用于多线程日志拼接,无需额外同步。它的所有公共方法(如 append()、insert()、toString())都加了 synchronized,保证同一实例被多个线程并发调用时不会出现数据错乱或内部状态不一致。
为什么 StringBuffer 能安全用于多线程日志场景
日志拼接通常涉及多个线程频繁调用 append() 追加内容(如时间、线程名、消息体),关键要求是:
- 字符序列最终拼接结果不丢失、不交叉(比如 A 线程写 "user=123",B 线程写 "err=timeout",不能变成 "userr=timeout23")
- 调用
toString()时能拿到当前完整、一致的快照
StringBuffer 满足这两点:每个方法独占锁,执行期间其他线程必须等待;内部 count 和 value[] 的修改与读取都在同步块内完成。
实际使用建议(避免常见误区)
- 不要共享同一个 StringBuffer 实例供所有线程无限追加 —— 日志量大时会成为性能瓶颈(串行化竞争),且缓冲区持续膨胀,可能 OOM。推荐按需创建(如每次日志生成一个新 StringBuffer)或配合对象池(如 Apache Commons Pool)复用
- 避免在同步块外缓存 StringBuffer 引用并跨线程传递 —— 比如把一个 StringBuffer 放进 ThreadLocal 后又把它传给另一个线程操作,会破坏线程隔离性
- toString() 是安全的,但注意它返回的是新 String 对象,不是对内部 char[] 的引用 —— 所以多线程同时调用 toString() 不会影响彼此,也不会导致脏读
对比 StringBuilder:什么情况下不该用 StringBuffer
如果日志拼接严格限定在单线程内(例如 SLF4J 的 MDC 上下文处理、或异步日志器内部格式化阶段),用 StringBuilder 更高效——它无同步开销,性能通常高 10%~30%。只有明确需要跨线程共享并修改**同一个**拼接容器时,才选 StringBuffer。
立即学习“Java免费学习笔记(深入)”;
一个典型安全用法示例
以下代码片段中,每个线程独立创建 StringBuffer,拼接后转为字符串再交给日志框架,既安全又无竞争:
public String formatLog(String level, String msg) {
StringBuffer sb = new StringBuffer(128); // 预估容量,减少扩容
sb.append('[').append(LocalDateTime.now()).append("] ");
sb.append('[').append(Thread.currentThread().getName()).append("] ");
sb.append(level).append(" - ").append(msg);
return sb.toString(); // 安全,返回不可变副本
}这种模式无需锁、无共享、无状态残留,是多线程日志拼接中最常用也最推荐的方式。


















