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 锁获取有不可忽略的开销
相关推荐
happymaker06264 小时前
SpringBoot学习日记——DAY06(整合MyBatisPlus的其他功能)
java·spring boot·学习
砍材农夫5 小时前
物联网 基于netty核心实战-会话管理
后端
元宝骑士5 小时前
MySQL 8.0 递归 CTE:树形结构一键生成层级 Path 并更新回表
后端·mysql
Refrain_zc5 小时前
Android 播放器进度条改造实践:句级音频列表映射秒级时间轴
java
我命由我123455 小时前
Bugly - Bugly 基本使用( App 质量追踪平台)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
宋哥转AI5 小时前
Spring AI Graph:从0到Supervisor(一)RAG子图+Supervisor路由踩坑全记录
java·agent
Mahir085 小时前
MyBatis 深度解密:从执行流程到底层原理全解
java·后端·面试·mybatis
菜菜的顾清寒5 小时前
力扣hot100(37)栈-有效的括号
java·开发语言
罗超驿5 小时前
9.LeetCode 209. 长度最小的子数组 | 滑动窗口专题详解
java·算法·leetcode·面试