<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      面試中經(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)線程安全的呢?看下源代碼就明白了了。。。

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          乱淫亲戚小说全篇 | 总裁含着她的乳尖h在线观看 | 欧美三级电影中文字幕 | 囚禁调教分腿器扩张 | 大尺度视频在线观看123 | 77777一区二区三99 | 日本婷婷| 精品 码红桃二区三区 | 男模裸体照 | 又长又粗又爽视频 |