我们都知道:
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

那么,为什么StringBuilder不是线程安全的。

分析

StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer的char数组是可变的。


首先,我们可以了解一下StringBuffer和StringBuilder的异同。
String,StringBuilder以及StringBuffer这三个类之间的区别
下面,我们通过一段代码来看看如果我们多线程操作了StringBuilder对象会发生什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StringBuilderTest {
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++){
stringBuilder.append("a");
stringBuffer.append("b");
}
}
}).start();
}
Thread.sleep(100);
System.out.println("stringBuilder的长度:"+stringBuilder.length());
System.out.println("stringBuffer的长度:"+stringBuffer.length());
}
}

在这段代码中,我们创建了10个进程,每个进程循环10000次向StringBuilder对象stringBuilder和StringBuffer对象stringBuffer中append字符a、b。
如果其中没有出错的话,最后的输出字符串的长度应该是100000。
但实际上的结果是:
StringBuilderTest
那么为什么结果会和预期的值不一样呢。
我们先来看一看StringBuilder的append()方法:

StringBuilder的append()方法调用的是父类AbstractStringBuilder的append()方法:

我们可以看到,其中 count += len 不是一个原子操作。当多个线程同时执行的时候,取得的count值都是一样的,但是执行完加法后赋值给count,count的值只增加了1,但是实际上却是多个线程同时执行了这一步,这就是为什么结果比预计值要小的原因。
同理就可以分析出StringBuffer为什么是线程安全的了。