面試中經(jīng)常問到的一個(gè)問題:StringBuilder和StringBuffer的區(qū)別是什么?
我們非常自信的說出:StringBuilder是線程不安全的,StirngBuffer是線程安全的
面試官:StringBuilder不安全的點(diǎn)在哪兒?
這時(shí)候估計(jì)就啞巴了。。。
分析
StringBuffer和StringBuilder的實(shí)現(xiàn)內(nèi)部是和String內(nèi)部一樣的,都是通過 char[]數(shù)組的方式;不同的是String的char[]
數(shù)組是通過final關(guān)鍵字修飾的是不可變的,而StringBuffer和StringBuilder的char[]數(shù)組是可變的。
首先我們看下邊這個(gè)例子:
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()); } }
直覺告訴我們輸出結(jié)果應(yīng)該是10000000,但是實(shí)際運(yùn)行結(jié)果并非我們所想。
從上圖可以看到輸出結(jié)果是9970698,并非是我們預(yù)期的1000000,并且還拋出了一個(gè)異常ArrayIndexOutOfBoundsException
{非必現(xiàn)}
為什么輸出結(jié)果并非預(yù)期值?
我們先看一下StringBuilder的兩個(gè)成員變量(這兩個(gè)成員變量實(shí)際上是定義在AbstractStringBuilder里面的,StringBuilder
和StringBuffer都繼承了AbstractStringBuilder)
StringBuilder的append方法
StringBuilder的append方法調(diào)用了父類的append方法
我們直接看第七行代碼,count += len; 不是一個(gè)原子操作,實(shí)際執(zhí)行流程為
* 首先加載count的值到寄存器
* 在寄存器中執(zhí)行 +1操作
* 將結(jié)果寫入內(nèi)存
假設(shè)我們count的值是10,len的值為1,兩個(gè)線程同時(shí)執(zhí)行到了第七行,拿到的值都是10,執(zhí)行完加法運(yùn)算后將結(jié)果賦值給count
,所以兩個(gè)線程最終得到的結(jié)果都是11,而不是12,這就是最終結(jié)果小于我們預(yù)期結(jié)果的原因。
為什么會(huì)拋出ArrayIndexOutOfBoundsException異常?
我們看回AbstractStringBuilder的追加()方法源碼的第五行,ensureCapacityInternal()方法是檢查StringBuilder的對象的原字符數(shù)組的容量能不能盛下新的字符串,如果盛不下就調(diào)用expandCapacity()方法對字符數(shù)組進(jìn)行擴(kuò)容。
private void ensureCapacityInternal(int minimumCapacity) { //溢出意識代碼 if
(minimumCapacity - value .length> 0) expandCapacity(minimumCapacity); }
擴(kuò)容的邏輯就是新一個(gè)新的字符數(shù)組,新的字符數(shù)組的容量是原來字符數(shù)組的兩倍再加2,再通過System.arryCopy()函數(shù)將原數(shù)組的內(nèi)容復(fù)制到新數(shù)組,最后將指針指向新的字符數(shù)組。
void expandCapacity(int minimumCapacity) { //計(jì)算新的容量 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]; //拷貝數(shù)組 System.arraycopy(original, 0,copy, 0,
Math.min(original.length,newLength)); 返回 副本; }
AbstractStringBuilder的追加()方法源碼的第六行,是將字符串對象里面字符數(shù)組里面的內(nèi)容拷貝到StringBuilder的對象的字符數(shù)組里面,代碼如下:
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); }
拷貝流程見下圖
假設(shè)現(xiàn)在有兩個(gè)線程同時(shí)執(zhí)行了StringBuilder的append()方法,兩個(gè)線程都執(zhí)行完了第五行的ensureCapacityInternal()
方法,此刻count=5
這個(gè)時(shí)候線程1的cpu時(shí)間片用完了,線程2繼續(xù)執(zhí)行。線程2執(zhí)行完整個(gè)append()方法后count變成6了。
線程1繼續(xù)執(zhí)行第六行的str.getChars()方法的時(shí)候拿到的count值就是6了,執(zhí)行char[]數(shù)組拷貝的時(shí)候就會(huì)拋出
ArrayIndexOutOfBoundsException異常。
至此,StringBuilder為什么不安全已經(jīng)分析完了。如果我們將測試代碼的StringBuilder對象換成StringBuffer對象會(huì)輸出什么呢?
結(jié)果肯定是會(huì)輸出 1000000,至于StringBuffer是通過什么手段實(shí)現(xiàn)線程安全的呢?看下源代碼就明白了了。。。
熱門工具 換一換