1 理论证明
StringBuilder存放字符串的属性是char value[], 这在其父类AbstractStringBuilder里面可以查看:
数组都是创建在堆中,堆中的内存任意线程都可以访问。而且这个字符数组没有像String类那样用final修饰,所以任意线程都可以修改,同一时刻可能有多个线程在访问这个快内存区域。说明StringBuilder不是线性安全的,是指char[] value字符数组对于的堆中的那块内存区域不是线性安全的。在一个线程处理的过程中,另一个线程可能有已经把这个内存区域修改了。
2 代码证明
class T implements Runnable {
StringBuilder sb;
public T(StringBuilder sb) {
this.sb = sb;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
sb.append('a');
}
}
}
首先,创建一个类T,实现Runnable接口,这个线程有一个属性StringBuilder sb,后面不同的线程会共享同一个sb。
run方法用于执行具体的逻辑,这里像sb中append 1000个’a’
下面看main方法里面怎么写的:
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
T t = new T(sb);
for (int i = 0; i < 5; i++) {
new Thread(t).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sb.length());
}
}
main方法里面创建了5个线程来向sb中append 字符,每个线程append 1000次,那么5个线程执行,最终结果sb中应该有5000个’a’。
睡眠一秒后打印sb的长度:
注:这里的结果不一定是4961,也可能就是5000,也可能是其它数,多运行几次会发现不同的结果
结果解释
最终结果sb中没有5000个’a’:因为append方法没有用synchronized修饰,那么某个时刻可能有多个线程在执行append方法,这是append方法实现的逻辑:
可能线程1在执行value[count++] = c的时候,count++还没有完全写入count中时,线程2又拿到count,此时线程2就会覆盖线程1的结果
还有一种情况是数组越界:当大量线程同时涌进appead方法,都通过了esureCapacityInternal检查,然后大量线程同时执行value[count++],这样,就会造成数组越界