<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么StringBuilder是線程不安全的?

          共 3836字,需瀏覽 8分鐘

           ·

          2020-09-30 16:10

          在前面的面試題講解中我們對(duì)比了String、StringBuilder和StringBuffer的區(qū)別,其中一項(xiàng)便提到StringBuilder是非線程安全的,那么是什么原因?qū)е铝薙tringBuilder的線程不安全呢?

          原因分析

          如果你看了StringBuilder或StringBuffer的源代碼會(huì)說,因?yàn)镾tringBuilder在append操作時(shí)并未使用線程同步,而StringBuffer幾乎大部分方法都使用了synchronized關(guān)鍵字進(jìn)行方法級(jí)別的同步處理。

          上面這種說法肯定是正確的,對(duì)照一下StringBuilder和StringBuffer的部分源代碼也能夠看出來。

          StringBuilder的append方法源代碼:

          @Overridepublic StringBuilder append(String str) {    super.append(str);    return this;}

          StringBuffer的append方法源代碼:

          @Overridepublic synchronized StringBuffer append(String str) {    toStringCache = null;    super.append(str);    return this;}

          對(duì)于上面的結(jié)論肯定是沒什么問題的,但并沒有解釋是什么原因?qū)е铝薙tringBuilder的線程不安全?為什么要使用synchronized來保證線程安全?如果不是用會(huì)出現(xiàn)什么異常情況?

          下面我們來逐一講解。

          異常示例

          我們先來跑一段代碼示例,看看出現(xiàn)的結(jié)果是否與我們的預(yù)期一致。

          @Testpublic void test() throws InterruptedException {  StringBuilder sb = new StringBuilder();  for (int i = 0; i < 10; i++) {    new Thread(() -> {      for (int j = 0; j < 1000; j++) {        sb.append("a");      }    }).start();  }  // 睡眠確保所有線程都執(zhí)行完  Thread.sleep(1000);  System.out.println(sb.length());}

          上述業(yè)務(wù)邏輯比較簡(jiǎn)單,就是構(gòu)建一個(gè)StringBuilder,然后創(chuàng)建10個(gè)線程,每個(gè)線程中拼接字符串“a”1000次,理論上當(dāng)線程執(zhí)行完成之后,打印的結(jié)果應(yīng)該是10000才對(duì)。

          但多次執(zhí)行上面的代碼打印的結(jié)果是10000的概率反而非常小,大多數(shù)情況都要少于10000。同時(shí),還有一定的概率出現(xiàn)下面的異常信息“

          Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException  at java.lang.System.arraycopy(Native Method)  at java.lang.String.getChars(String.java:826)  at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)  at java.lang.StringBuilder.append(StringBuilder.java:136)  at com.secbro2.strings.StringBuilderTest.lambda$test$0(StringBuilderTest.java:18)  at java.lang.Thread.run(Thread.java:748)9007

          線程不安全的原因

          StringBuilder中針對(duì)字符串的處理主要依賴兩個(gè)成員變量char數(shù)組value和count。StringBuilder通過對(duì)value的不斷擴(kuò)容和count對(duì)應(yīng)的增加來完成字符串的append操作。

          // 存儲(chǔ)的字符串(通常情況一部分為字符串內(nèi)容,一部分為默認(rèn)值)char[] value;
          // 數(shù)組已經(jīng)使用數(shù)量int count;

          上面的這兩個(gè)屬性均位于它的抽象父類AbstractStringBuilder中。

          如果查看構(gòu)造方法我們會(huì)發(fā)現(xiàn),在創(chuàng)建StringBuilder時(shí)會(huì)設(shè)置數(shù)組value的初始化長(zhǎng)度。

          public StringBuilder(String str) {    super(str.length() + 16);    append(str);}

          默認(rèn)是傳入字符串長(zhǎng)度加16。這就是count存在的意義,因?yàn)閿?shù)組中的一部分內(nèi)容為默認(rèn)值。

          當(dāng)調(diào)用append方法時(shí)會(huì)對(duì)count進(jìn)行增加,增加值便是append的字符串的長(zhǎng)度,具體實(shí)現(xiàn)也在抽象父類中。

          public AbstractStringBuilder append(String str) {    if (str == null)        return appendNull();    int len = str.length();    ensureCapacityInternal(count + len);    str.getChars(0, len, value, count);    count += len;    return this;}

          我們所說的線程不安全的發(fā)生點(diǎn)便是在append方法中count的“+=”操作。我們知道該操作是線程不安全的,那么便會(huì)發(fā)生兩個(gè)線程同時(shí)讀取到count值為5,執(zhí)行加1操作之后,都變成6,而不是預(yù)期的7。這種情況一旦發(fā)生便不會(huì)出現(xiàn)預(yù)期的結(jié)果。

          拋異常的原因

          回頭看異常的堆棧信息,回發(fā)現(xiàn)有這么一行內(nèi)容:

          at java.lang.String.getChars(String.java:826)

          對(duì)應(yīng)的代碼就是上面AbstractStringBuilder中append方法中的代碼。對(duì)應(yīng)方法的源代碼如下:

          public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {    if (srcBegin < 0) {        throw new StringIndexOutOfBoundsException(srcBegin);    }    if (srcEnd > value.length) {        throw new StringIndexOutOfBoundsException(srcEnd);    }    if (srcBegin > srcEnd) {        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);    }    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}

          其實(shí)異常是最后一行arraycopy時(shí)JVM底層發(fā)生的。arraycopy的核心操作就是將傳入的String對(duì)象copy到value當(dāng)中。

          而異常發(fā)生的原因是明明value的下標(biāo)只到6,程序卻要訪問和操作下標(biāo)為7的位置,當(dāng)然就跑異常了。

          那么,為什么會(huì)超出這么一個(gè)位置呢?這與我們上面講到到的count被少加有關(guān)。在執(zhí)行str.getChars方法之前還需要根據(jù)count校驗(yàn)一下當(dāng)前的value是否使用完畢,如果使用完了,那么就進(jìn)行擴(kuò)容。append中對(duì)應(yīng)的方法如下:

          ensureCapacityInternal(count + len);

          ensureCapacityInternal的具體實(shí)現(xiàn):

          private void ensureCapacityInternal(int minimumCapacity) {    // overflow-conscious code    if (minimumCapacity - value.length > 0) {        value = Arrays.copyOf(value,                newCapacity(minimumCapacity));    }}

          count本應(yīng)該為7,value長(zhǎng)度為6,本應(yīng)該觸發(fā)擴(kuò)容。但因?yàn)椴l(fā)導(dǎo)致count為6,假設(shè)len為1,則傳遞的minimumCapacity為7,并不會(huì)進(jìn)行擴(kuò)容操作。這就導(dǎo)致后面執(zhí)行str.getChars方法進(jìn)行復(fù)制操作時(shí)訪問了不存在的位置,因此拋出異常。

          這里我們順便看一下擴(kuò)容方法中的newCapacity方法:

          private int newCapacity(int minCapacity) {    // overflow-conscious code    int newCapacity = (value.length << 1) + 2;    if (newCapacity - minCapacity < 0) {        newCapacity = minCapacity;    }    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)        ? hugeCapacity(minCapacity)        : newCapacity;}

          除了校驗(yàn)部分,最核心的就是將新數(shù)組的長(zhǎng)度擴(kuò)充為原來的兩倍再加2。把計(jì)算所得的新長(zhǎng)度作為Arrays.copyOf的參數(shù)進(jìn)行擴(kuò)容。

          小結(jié)

          經(jīng)過上面的分析,是不是真正了解了StringBuilder的線程不安全的原因?我們?cè)趯W(xué)習(xí)和實(shí)踐的過程中,不僅要知道一些結(jié)論,還要知道這些結(jié)論的底層原理,更重要的是學(xué)會(huì)分析底層原理的方法。

          瀏覽 63
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本老女人操逼视频 | 亚洲综合婷婷深深 | 六区,七区视频在线播放 | 黄色在线免费一级视频 | 一级黄色片在线观看 |