StringBuffer和StringBuilder之StringBuilder为什么线程不安全
Posted jaci
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了StringBuffer和StringBuilder之StringBuilder为什么线程不安全相关的知识,希望对你有一定的参考价值。
StringBuffer和StringBuilder的区别在哪里?
StringBuffer是线程安全的,StringBuilder是线程不安全的。
那么StringBuilder不安全在哪里?在想这个问题前,我们要知道StringBuffer和StringBuilder的内部实现和String类是一样的,都是通过一个char数组存储字符串,不同的是String类的char数组是final修饰的,是不可变的;但是StringBuffer和StringBuilder的char数组是可变的。
例子:
多线程操作StringBuilder对象
1 public class StringBuilderDemo 2 public static void main(String[] args) throws InterruptedException 3 StringBuilder sBuilder = new StringBuilder(); 4 for(int i=0;i<10;i++) 5 new Thread(new Runnable() 6 @Override 7 public void run() 8 for(int j=0;j<1000;j++) 9 sBuilder.append("a"); 10 11 12 ).start(); 13 14 Thread.sleep(100); 15 System.out.println(sBuilder.length()); 16 17
运行结果
7346
这段代码创建了10个线程,每个线程循环1000次往StringBuilder对象里append字符,正常情况应输出10000;但是结果是小于预期结果,在实际运行中还可能抛出异常“ArrayIndexOutOfBoundsException”
那么,问题来了,为什么输出值和预期不一样?为什么会抛出异常“ArrayIndexOutOfBoundsException”?
来看第一个问题:
StringBuilder的两个成员变量(这两个变量定义在AbstractStringBuilder里面,StringBuilder和StringBuffer都继承了AbstractStringBuilder)
//存储字符串的具体内容 char[] value; //已经使用的字符数组的数量 int count;
在看StringBuilder的append()方法
@Override public StringBuilder append(String str) super.append(str); return this;
StringBuilder的append()方法调用AbstractStringBuilder的append()方法
1 public AbstractStringBuilder append(String str) 2 if(str==null) 3 return appendNull(); 4 5 int len = str.length(); 6 ensureCapacityInternal(count + len); 7 str.getChars(0,len,value,count); 8 count += len; 9 return this; 10
看第8行,count += len; 假设count为10,len值为1,两个线程同时执行到这里,拿到的count值都为10,执行完加法运算后将值赋值给count,结果两个线程执行完后的count值为11,而不是12,这就是输出值比预期值要小的原因。
看第二个问题
回看AbstractStringBuilder的append()方法第6行
ensureCapacityInternal()方法是检查StringBuilder()对象的原char数组的容量能不能装下新的字符串,如果装不下就调用expandCapacity()方法对char数组进行扩容
private void ensureCapacityInternal(int minimumCapacity) if(minimumCapacity - value.length>0) expandCapacity(minimumCapacity);
扩容即是new一个新的char数组,在将原来的数组内容复制到新的数组,最后将指针指向新的char数组
void expandCapacity(int minimunCapacity) //计算新的容量 int newCapacity = value.length*2+2; //省略一些检查逻辑 ... value = Array.copyOf(value,newCapacity);
Array.copyOf()方法
public static char[] copyOf(char[] original,int newLength) char[] copy = new char[newLength]; //拷贝数组 System.arraycopy(original,0,copy,0,Mathmin(original.length,newLength)); return copy;
AbstractStringBuilder的append()方法第7行,是将String对象的char数组内容拷贝到StringBuilder对象的char数组里面
getChars()方法
public void getChars(int srcBegin,int srcEnd,char dst[],int dstBegin) // ... System.arraycopy(value,srcBegin,dst,dstBegin,srcEnd-srcBegin);
拷贝流程:
假设现在有两个线程同时执行StringBuilder的append()方法,两个线程都执行完了ensureCapacityInternal()方法,此时count=5
此时线程1的cpu时间片用完了,线程2继续执行,线程2执行完append()方法后变为6
线程1继续执行str,getChars()方法的时候拿到的值是6,执行char数组拷贝的时候就会抛出异常ArrayIndexOutOfBoundsException。
解释完毕!
那么将StringBuilder换成StringBuffer会发生什么呢?
StringBuffer可是个线程安全的StringBuffer,当然是输出10000啦。
以上是关于StringBuffer和StringBuilder之StringBuilder为什么线程不安全的主要内容,如果未能解决你的问题,请参考以下文章
StringBuffer 和 StringBuilder 总结