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數組里面

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM