核心概念
❌ StringBuilder
是线程不安全的!
StringBuilder
专为单线程高性能字符串操作设计- 所有修改方法(如
append
,insert
,delete
)没有同步机制 - 在多线程环境下并发修改会导致:
- 数据丢失
- 字符串错乱
- 甚至
IndexOutOfBoundsException
✅ 正确选择:StringBuffer
StringBuffer
是StringBuilder
的线程安全版本- 所有关键方法都用
synchronized
修饰 - 保证多线程操作的原子性和可见性
⚠️ 替代方案:并发工具类
StringJoiner
(Java 8+)Collectors.joining()
(配合 Stream)ThreadLocal<StringBuilder>
(每个线程独立实例)
操作步骤(非常详细)
步骤 1:识别多线程场景
// 示例:多个线程同时拼接日志
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
// ❌ 危险!多个线程共享同一个 StringBuilder
unsafeBuilder.append(Thread.currentThread().getName())
.append(": log").append(i).append("\n");
}
};
步骤 2:选择正确的线程安全方案
方案一:使用 StringBuffer
(推荐用于简单场景)
// 共享的线程安全缓冲区
StringBuffer safeBuffer = new StringBuffer();
// 多个线程可以安全地拼接
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
for (int j = 0; j < 10; j++) {
safeBuffer.append(Thread.currentThread().getName())
.append("-msg").append(j).append("\n");
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// 获取最终结果
String result = safeBuffer.toString();
方案二:使用 synchronized
块保护 StringBuilder
StringBuilder sb = new StringBuilder();
Object lock = new Object(); // 锁对象
executor.submit(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) { // 同步块
sb.append(Thread.currentThread().getName())
.append(": safe").append(i).append("\n");
}
}
});
方案三:使用 ThreadLocal<StringBuilder>
(高性能推荐)
// 每个线程拥有独立的 StringBuilder 实例
private static final ThreadLocal<StringBuilder> threadLocalBuilder =
ThreadLocal.withInitial(() -> new StringBuilder(1024));
// 线程任务
executor.submit(() -> {
StringBuilder localSb = threadLocalBuilder.get();
for (int i = 0; i < 10; i++) {
localSb.append(Thread.currentThread().getName())
.append(": local").append(i).append("\n");
}
// 注意:需要将各线程结果合并
synchronized (resultBuffer) {
resultBuffer.append(localSb);
}
threadLocalBuilder.remove(); // 防止内存泄漏
});
方案四:使用 StringJoiner
(Java 8+,适合简单拼接)
// StringJoiner 本身不是线程安全的,但可配合同步使用
StringJoiner joiner = new StringJoiner(",", "[", "]");
executor.submit(() -> {
String[] items = {"a", "b", "c"};
synchronized (joiner) {
for (String item : items) {
joiner.add(item + "-" + Thread.currentThread().getName());
}
}
});
步骤 3:合并结果(如果需要)
// 如果使用 ThreadLocal 或多个缓冲区,需要合并
StringBuffer finalResult = new StringBuffer();
for (StringBuffer buffer : threadBuffers) {
finalResult.append(buffer);
}
步骤 4:资源清理
// 关闭线程池
if (executor != null) {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
常见错误
❌ 错误 1:直接在多线程中使用 StringBuilder
StringBuilder unsafe = new StringBuilder();
// 多个线程同时调用 append —— 可能导致数据损坏!
❌ 错误 2:只同步部分操作
synchronized (lock) {
sb.append("prefix");
}
sb.append("suffix"); // ❌ 未同步,仍不安全!
❌ 错误 3:忘记清理 ThreadLocal
ThreadLocal<StringBuilder> tl = new ThreadLocal<>();
// 使用后未调用 remove() —— 可能导致内存泄漏
❌ 错误 4:过度同步影响性能
// 每次 append 都同步 —— 性能极差
synchronized (sb) {
sb.append(data);
}
注意事项
- ✅ 绝对禁止:多个线程共享同一个
StringBuilder
实例 - ⚠️
StringBuffer
虽然安全,但性能低于StringBuilder
(约慢20-50%) - ✅
ThreadLocal<StringBuilder>
是高性能推荐方案 - ⚠️ 使用
synchronized
时,确保整个操作序列都在同步块内 - ✅ 合并结果时,仍需考虑线程安全(如用
StringBuffer
接收)
使用技巧
1. 预设容量提升性能
// 减少内部数组扩容次数
StringBuffer sb = new StringBuffer(4096);
2. 使用 Collectors.joining()
(函数式风格)
List<String> data = Arrays.asList("a", "b", "c");
String result = data.parallelStream()
.map(s -> s + "-processed")
.collect(Collectors.joining(",", "[", "]"));
// 线程安全由 Stream 框架保证
3. 批量拼接减少同步开销
synchronized (buffer) {
// 一次性拼接多个内容,减少同步次数
for (String item : batch) {
buffer.append(item).append("\n");
}
}
最佳实践与性能优化
方案 | 适用场景 | 性能 | 安全性 | 推荐指数 |
---|---|---|---|---|
StringBuffer |
简单场景,低并发 | 中等 | ✅ | ⭐⭐⭐⭐ |
synchronized + StringBuilder |
需要 StringBuilder 特性 |
中等 | ✅ | ⭐⭐⭐ |
ThreadLocal<StringBuilder> |
高并发,高性能要求 | 高 | ✅ | ⭐⭐⭐⭐⭐ |
Collectors.joining() |
Stream 处理 | 中高 | ✅ | ⭐⭐⭐⭐ |
StringJoiner + synchronized |
简单分隔拼接 | 中等 | ✅ | ⭐⭐⭐ |
🚀 性能优化建议
优先使用
ThreadLocal<StringBuilder>
- 避免锁竞争
- 每个线程独立操作
- 最终合并成本可控
预估容量减少扩容
new StringBuilder(estimatedSize)
批量操作减少同步
- 积累一定数据后再同步写入
考虑无锁设计
- 使用
ConcurrentLinkedQueue<String>
收集片段 - 最后统一拼接
- 使用
完整示例代码
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class StringBuilderThreadSafety {
// 方案1: StringBuffer (线程安全)
private static final StringBuffer STRING_BUFFER = new StringBuffer();
// 方案2: ThreadLocal (高性能推荐)
private static final ThreadLocal<StringBuilder> THREAD_LOCAL_SB =
ThreadLocal.withInitial(() -> new StringBuilder(1024));
// 结果合并缓冲区
private static final StringBuffer FINAL_RESULT = new StringBuffer();
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(4);
// --- 方案一:StringBuffer ---
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
for (int j = 0; j < 5; j++) {
STRING_BUFFER.append(Thread.currentThread().getName())
.append("-SB-").append(j).append("\n");
}
});
}
// --- 方案二:ThreadLocal + 合并 ---
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
StringBuilder local = THREAD_LOCAL_SB.get();
for (int j = 0; j < 5; j++) {
local.append(Thread.currentThread().getName())
.append("-TL-").append(j).append("\n");
}
// 合并到最终结果(同步)
synchronized (FINAL_RESULT) {
FINAL_RESULT.append(local);
}
THREAD_LOCAL_SB.remove(); // 清理
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("=== StringBuffer 结果 ===");
System.out.println(STRING_BUFFER);
System.out.println("=== ThreadLocal 结果 ===");
System.out.println(FINAL_RESULT);
}
}
总结
项目 | 说明 |
---|---|
核心原则 | StringBuilder 不能用于多线程共享 |
安全替代 | StringBuffer 、ThreadLocal<StringBuilder> |
最佳实践 | 高并发用 ThreadLocal ,简单场景用 StringBuffer |
性能排序 | ThreadLocal > StringBuilder > StringBuffer |
常见陷阱 | 忘记同步、ThreadLocal 内存泄漏、部分同步 |
✅ 一句话掌握:
永远不要在多线程中共享StringBuilder
。选择StringBuffer
保证安全,或使用ThreadLocal<StringBuilder>
实现高性能并发拼接。