面試中常常問到的一個問題:StringBuilder
和StringBuffer
的區別是什麼?
咱們很是自信的說出:StringBuilder
是線程不安全的,StirngBuffer
是線程安全的
面試官:StringBuilder
不安全的點在哪兒?
這時候估計就啞吧了。。。
java
StringBuffer
和StringBuilder
的實現內部是和String
內部同樣的,都是經過 char[]
數組的方式;不一樣的是String
的char[]
數組是經過final
關鍵字修飾的是不可變的,而StringBuffer
和StringBuilder
的char[]
數組是可變的。面試
首先咱們看下邊這個例子:數組
public class Test { public static void main(String[] args) throws InterruptedException { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++){ new Thread(() -> { for (int j = 0; j < 1000; j++){ stringBuilder.append("a"); } }).start(); } Thread.sleep(100L); System.out.println(stringBuilder.length()); } }
直覺告訴咱們輸出結果應該是10000000
,可是實際運行結果並不是咱們所想。安全
從上圖能夠看到輸出結果是9970698
,並不是是咱們預期的1000000
,而且還拋出了一個異常ArrayIndexOutOfBoundsException
{非必現}app
咱們先看一下StringBuilder
的兩個成員變量(這兩個成員變量其實是定義在AbstractStringBuilder
裏面的,StringBuilder
和StringBuffer
都繼承了AbstractStringBuilder
)函數
StringBuilder
的append
方法測試
StringBuilder
的append
方法調用了父類的append
方法ui
咱們直接看第七行代碼,count += len;
不是一個原子操做,實際執行流程爲線程
count
的值到寄存器+1
操做假設咱們count
的值是10
,len
的值爲1
,兩個線程同時執行到了第七行,拿到的值都是10
,執行完加法運算後將結果賦值給count
,因此兩個線程最終獲得的結果都是11
,而不是12
,這就是最終結果小於咱們預期結果的緣由。3d
咱們看回AbstractStringBuilder的追加()方法源碼的第五行,ensureCapacityInternal()方法是檢查StringBuilder的對象的原字符數組的容量能不能盛下新的字符串,若是盛不下就調用expandCapacity()方法對字符數組進行擴容。
private void ensureCapacityInternal(int minimumCapacity) { //溢出意識代碼 if (minimumCapacity - value .length> 0) expandCapacity(minimumCapacity); }
擴容的邏輯就是新一個新的字符數組,新的字符數組的容量是原來字符數組的兩倍再加2,再經過System.arryCopy()函數將原數組的內容複製到新數組,最後將指針指向新的字符數組。
void expandCapacity(int minimumCapacity) { //計算新的容量 int newCapacity = value .length * 2 + 2 ; //中間省略了一些檢查邏輯 ... value = Arrays.copyOf( value,newCapacity); }
Arrys.copyOf()方法
public static char [] copyOf(char [] original, int newLength) { char [] copy = new char [newLength]; //拷貝數組 System.arraycopy(original, 0,copy, 0, Math.min(original.length,newLength)); 返回 副本; }
AbstractStringBuilder的追加()方法源碼的第六行,是將字符串對象裏面字符數組裏面的內容拷貝到StringBuilder的對象的字符數組裏面,代碼以下:
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()
方法後count
變成6
了。
線程1
繼續執行第六行的str.getChars()
方法的時候拿到的count
值就是6
了,執行char[]
數組拷貝的時候就會拋出ArrayIndexOutOfBoundsException
異常。
至此,StringBuilder
爲何不安全已經分析完了。若是咱們將測試代碼的StringBuilder
對象換成StringBuffer
對象會輸出什麼呢?
結果確定是會輸出 1000000
,至於StringBuffer
是經過什麼手段實現線程安全的呢?看下源代碼就明白了了。。。