为何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时,会出现以下问题:
- 操作非原子性:
append的执行分为多步(转字符串、扩容、复制字符、更新count),线程A可能刚把字符写入value数组,还没来得及更新count,线程B就开始执行自己的append操作,干扰count的正确更新。 - 内存可见性问题:
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
相关产品推荐
相关产品推荐

