zoukankan      html  css  js  c++  java
  • StringBuilder的线程为什么不安全

    StringBuffer和StringBuilder的区别在哪里?

    StringBuffer是线程安全的,StringBuilder是线程不安全的。

    那么StringBuilder不安全在哪里?在想这个问题前,我们要知道StringBuffer和StringBuilder的内部实现和String类是一样的,都是通过一个char数组存储字符串,不同的是String类的char数组是final修饰的,是不可变的;但是StringBuffer和StringBuilder的char数组是可变的。

    例子:

    多线程操作StringBuilder对象

    public class StringBuilderDemo{
        public static void main(String[] args) throws InterruptedException{
            StringBuilder sBuilder = new StringBuilder();
            for(int i=0;i<10;i++){
                new Thread(new Runnable(){
                    @Override
                    public void run(){
                        for(int j=0;j<1000;j++){
                            sBuilder.append("a");
                        }
                    }
                }).start();
            }
            Thread.sleep(100);
            System.out.println(sBuilder.length());
        }
    }
    

    运行结果

    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()方法

    public AbstractStringBuilder append(String str){
        if(str==null){
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0,len,value,count);
        count += len;
        return this;
    }
    

    看第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数组里面

     str.getChars(0,len,value,count);
    

    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啦。

  • 相关阅读:
    【Lucene4.8教程之五】Luke
    【Tika基础教程之一】Tika基础教程
    【Lucene4.8教程之四】分析
    【Lucene4.8教程之六】QueryParser与Query子类:如何生成Query对象
    【Lucene4.8教程之三】搜索
    Java路径问题最终解决方案—可定位所有资源的相对路径寻址
    java.util.logging.Logger基础教程
    【Lucene4.8教程之二】索引
    【Lucene4.8教程之一】使用Lucene4.8进行索引及搜索的基本操作
    【Heritrix基础教程之4】开始一个爬虫抓取的全流程代码分析
  • 原文地址:https://www.cnblogs.com/a1439775520/p/13075714.html
Copyright © 2011-2022 走看看