面試中常常問到的一個問題:StringBuilder
和StringBuffer
的區別是什麼?咱們很是自信的說出:StringBuilder
是線程不安全的,StirngBuffer
是線程安全的面試官:StringBuilder
不安全的點在哪兒?這時候估計就啞吧了。。。
面試
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
,可是實際運行結果並不是咱們所想。app
從上圖能夠看到輸出結果是9970698
,並不是是咱們預期的1000000
,而且還拋出了一個異常ArrayIndexOutOfBoundsException
{非必現}函數
咱們先看一下StringBuilder
的兩個成員變量(這兩個成員變量其實是定義在AbstractStringBuilder
裏面的,StringBuilder
和StringBuffer
都繼承了AbstractStringBuilder
)測試
StringBuilder
的append
方法ui
StringBuilder
的append
方法調用了父類的append
方法spa
咱們直接看第七行代碼,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
是經過什麼手段實現線程安全的呢?看下源代碼就明白了了。。。