核心概念

StringBuilder线程不安全的!

  • StringBuilder 专为单线程高性能字符串操作设计
  • 所有修改方法(如 append, insert, delete没有同步机制
  • 在多线程环境下并发修改会导致:
    • 数据丢失
    • 字符串错乱
    • 甚至 IndexOutOfBoundsException

✅ 正确选择:StringBuffer

  • StringBufferStringBuilder线程安全版本
  • 所有关键方法都用 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 简单分隔拼接 中等 ⭐⭐⭐

🚀 性能优化建议

  1. 优先使用 ThreadLocal<StringBuilder>

    • 避免锁竞争
    • 每个线程独立操作
    • 最终合并成本可控
  2. 预估容量减少扩容

    new StringBuilder(estimatedSize)
    
  3. 批量操作减少同步

    • 积累一定数据后再同步写入
  4. 考虑无锁设计

    • 使用 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 不能用于多线程共享
安全替代 StringBufferThreadLocal<StringBuilder>
最佳实践 高并发用 ThreadLocal,简单场景用 StringBuffer
性能排序 ThreadLocal > StringBuilder > StringBuffer
常见陷阱 忘记同步、ThreadLocal 内存泄漏、部分同步

一句话掌握
永远不要在多线程中共享 StringBuilder。选择 StringBuffer 保证安全,或使用 ThreadLocal<StringBuilder> 实现高性能并发拼接。