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