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 锁获取有不可忽略的开销 |