You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何StringBuilder已有内容却返回长度为0?附测试代码

多线程下StringBuilder长度与内容矛盾的原因解析

先看你的测试代码:

public class SbVsSbf2 {

    static StringBuilder sb = new StringBuilder();
    static StringBuffer sbf = new StringBuffer();

    public static void main(String[] args) throws InterruptedException {
        // Thread 1
        Thread t1 = new Thread(() -> {
            for (int i = 0; i <= 23; i++) {
                sb.append(i);
                sbf.append(i);
            }
        });

        // Thread 2
        Thread t2 = new Thread(() -> {
            for (int i = 0; i <= 23; i++) {
                sb.append(i);
                sbf.append(i);
            }
        });

        t1.start();
        t2.start();

        System.out.println("StringBuilder: " + sb.length());
        System.out.println("StringBuffer: " + sbf.length());
        System.out.println("StringBuilder: " + sb);
        System.out.println("StringBuffer : " + sbf);

    }
}

核心矛盾的根源:线程不安全导致的内部状态不一致

StringBuilder的内部靠两个关键变量维护状态:

  • char[] value:存储实际字符的数组
  • int count:记录数组中有效字符的数量,length()方法直接返回这个值

而StringBuilder的所有方法都没有同步保护,当多个线程并发调用append时,会出现以下问题:

  1. 操作非原子性:append的执行分为多步(转字符串、扩容、复制字符、更新count),线程A可能刚把字符写入value数组,还没来得及更新count,线程B就开始执行自己的append操作,干扰count的正确更新。
  2. 内存可见性问题:count变量没有volatile修饰,主线程读取count时可能拿到的是CPU缓存里的旧值(比如初始的0),但调用toString()时,可能因为子线程已经把count的新值同步到主内存,或者toString()的逻辑触发了内存同步,导致能基于最新的count和value数组生成完整字符串,这就出现了"length返回0但能输出内容"的矛盾。

而StringBuffer的所有方法都加了synchronized同步锁,既保证了append操作的原子性,又通过同步机制保证了内存可见性,所以它的count和value状态始终一致,表现符合预期。

另外,你的代码还有一个隐藏问题:主线程启动子线程后立刻执行输出,没有等待子线程执行完毕,这会导致输出结果具有随机性(比如可能只执行了部分循环)。解决这个问题只需在输出前添加t1.join(); t2.join();,确保子线程的所有append操作完成后再输出。

总结

  • 多线程环境下必须用线程安全的StringBuffer,或者给StringBuilder手动加锁。
  • 线程不安全的类在并发场景下会出现各种诡异的状态不一致问题,本质是内部变量的修改没有原子性和可见性保障。

内容的提问来源于stack exchange,提问作者spatd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:01:18