5 StringBuffer —— 线程安全的可变字符串

StringBuffer ------ 线程安全的可变字符串

适用版本: JDK 8 难度等级: 基础 关键特征: synchronized 方法级同步、toString 缓存


一、StringBuffer 的定位

StringBuffer 是 AbstractStringBuilder 的两个直接子类之一,其核心使命是:

多线程环境中提供安全、可变的字符序列操作。

java 复制代码
public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

final 修饰意味着它不允许进一步继承,防止子类破坏线程安全语义。

String、StringBuilder、StringBuffer 三者关系

arduino 复制代码
                    ┌─────────────┐
                    │   String    │  不可变,线程天然安全
                    └─────────────┘
                    ┌──────────────────────┐
                    │ AbstractStringBuilder│  骨架实现(扩容/追加/删除等)
                    └──────────────────────┘
                     ↙                     ↘
        ┌──────────────┐           ┌──────────────┐
        │ StringBuffer │           │ StringBuilder│
        │ synchronized │           │   非同步     │
        │ 多线程安全    │           │ 单线程高效    │
        └──────────────┘           └──────────────┘

二、构造方法与初始容量

java 复制代码
public StringBuffer() {
    super(16);  // 默认容量 16
}

public StringBuffer(int capacity) {
    super(capacity);
}

public StringBuffer(String str) {
    super(str.length() + 16);  // 关键:字符串长度 + 16
    append(str);
}

public StringBuffer(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}
java 复制代码
public class CapacityDemo {
    public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer();
        System.out.println("空构造容量: " + sb1.capacity());  // 16

        StringBuffer sb2 = new StringBuffer("Hello");
        System.out.println("带字符串容量: " + sb2.capacity()); // 5 + 16 = 21
        System.out.println("有效长度: " + sb2.length());       // 5
    }
}

三、synchronized 同步策略

StringBuffer 中所有修改数据的公开方法 都加了 synchronized 关键字:

java 复制代码
public synchronized StringBuffer append(String str) {
    toStringCache = null;       // 清除缓存
    super.append(str);
    return this;
}

public synchronized StringBuffer insert(int offset, String str) {
    toStringCache = null;
    super.insert(offset, str);
    return this;
}

public synchronized StringBuffer delete(int start, int end) {
    toStringCache = null;
    super.delete(start, end);
    return this;
}

public synchronized StringBuffer reverse() {
    toStringCache = null;
    super.reverse();
    return this;
}

同步成本分析

java 复制代码
public class SyncOverheadDemo {
    public static void main(String[] args) throws InterruptedException {
        int iterations = 100_000;

        // 单线程 StringBuffer
        StringBuffer buffer = new StringBuffer();
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            buffer.append("x");
        }
        long tBuffer = System.nanoTime() - start;

        // 单线程 StringBuilder
        StringBuilder builder = new StringBuilder();
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            builder.append("x");
        }
        long tBuilder = System.nanoTime() - start;

        System.out.printf("StringBuffer: %6.2f ms%n", tBuffer / 1_000_000.0);
        System.out.printf("StringBuilder:%6.2f ms%n", tBuilder / 1_000_000.0);
    }
}

四、toString 缓存机制

这是 StringBuffer 与 StringBuilder 最核心的差异之一。

java 复制代码
// StringBuffer 中定义的缓存字段
private transient char[] toStringCache;

public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);  // 使用包私有构造,共享数组
}

// 任何修改操作都会清除缓存
public synchronized StringBuffer append(String str) {
    toStringCache = null;  // 失效缓存
    super.append(str);
    return this;
}

toStringCache 仅存在于 StringBuffer 中,因为只有多线程场景下重复调用 toString() 有利可图。StringBuilder 是单线程用途,不需要这份额外开销:

java 复制代码
// StringBuilder 的 toString ------ 每次都创建新副本
public String toString() {
    return new String(value, 0, count);  // 不缓存,不共享
}

缓存带来的性能差异

java 复制代码
public class ToStringCacheDemo {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello World");
        int iterations = 10_000_000;

        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            sb.toString();  // 第二次起命中 toStringCache
        }
        long elapsed = System.nanoTime() - start;
        System.out.printf("StringBuffer.toString × %d: %.2f ms%n",
                iterations, elapsed / 1_000_000.0);
    }
}

五、序列化机制

StringBuffer 使用自定义的序列化协议:

java 复制代码
private static final java.io.ObjectStreamField[] serialPersistentFields = {
    new java.io.ObjectStreamField("value", char[].class),
    new java.io.ObjectStreamField("count", Integer.TYPE),
    new java.io.ObjectStreamField("shared", Boolean.TYPE),
};

private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    java.io.ObjectOutputStream.PutField fields = s.putFields();
    fields.put("value", value);
    fields.put("count", count);
    fields.put("shared", false);
    s.writeFields();
}

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    java.io.ObjectInputStream.GetField fields = s.readFields();
    value = (char[]) fields.get("value", null);
    count = fields.get("count", 0);
}

serialPersistentFields 声明了参与序列化的字段。putFields/writeFields 提供了比 defaultWriteObject 更精细的控制能力。


六、综合实战:并发日志收集器

java 复制代码
import java.util.concurrent.*;

/**
 * 基于 StringBuffer 的线程安全日志收集器
 * 演示多写一读场景下的正确使用
 */
public class ConcurrentLogCollector {

    private final StringBuffer logBuffer = new StringBuffer(4096);
    private final ScheduledExecutorService flusher
            = Executors.newSingleThreadScheduledExecutor();

    public ConcurrentLogCollector() {
        // 每 5 秒自动冲洗到控制台
        flusher.scheduleAtFixedRate(this::flush, 5, 5, TimeUnit.SECONDS);
    }

    public void log(String level, String message) {
        // StringBuffer 自身是线程安全的,多个线程可安全并发调用
        logBuffer.append(java.time.LocalTime.now())
                .append(" [").append(level).append("] ")
                .append(message)
                .append(System.lineSeparator());
    }

    public synchronized String flush() {
        if (logBuffer.length() == 0) {
            return "";
        }
        String snapshot = logBuffer.toString();
        logBuffer.setLength(0);  // 清空缓冲区
        return snapshot;
    }

    public void shutdown() {
        flusher.shutdown();
        System.out.println("=== 最终日志冲洗 ===");
        System.out.print(flush());
    }

    public static void main(String[] args) throws InterruptedException {
        ConcurrentLogCollector collector = new ConcurrentLogCollector();

        ExecutorService workers = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            workers.submit(() -> {
                collector.log("INFO", "任务 #" + taskId + " 执行中");
            });
        }

        workers.shutdown();
        workers.awaitTermination(3, TimeUnit.SECONDS);
        collector.shutdown();
    }
}

七、面试高频考点

问题 关键要点
StringBuffer vs StringBuilder Buffer 所有公开方法 synchronized,Builder 无同步
toStringCache 的作用 缓存 toString 结果,避免重复创建 char\[\]
默认容量 无参构造:16;传入字符串:length + 16
为什么 final 类 防止子类破坏同步语义
序列化方式 自定义 writeObject/readObject + ObjectStreamField
单线程是否推荐 StringBuffer 不推荐,synchronized 锁获取有不可忽略的开销
相关推荐
画中有画5 小时前
软件架构中质量属性(性能、安全、可扩展性)的权衡设计
java·运维·安全
Shaoxi Zhang6 小时前
JAVA学习笔记035——对象和JSON格式
java·笔记·学习
赵丙双6 小时前
CountDownLatch 源码分析
java·aqs·countdownlatch
Python私教6 小时前
从表格到管理系统:别先写页面,先补齐权限、流程和审计
数据库·后端·架构
余额瞒着我当琳6 小时前
C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve
java·开发语言·c++
thefool1122666 小时前
翻转二叉树
java
北斗落凡尘7 小时前
LangGraph 入门实战(12)--使用MCP
后端·python·langchain
喜欢打篮球的普通人7 小时前
LLVM Backend Lowering 从入门到实战:把 IR 变成机器码的完整链路
android·java·数据库
FL16238631298 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
jufeng13078 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine