中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

StringBuilder是線程不安全的原因是什么

發布時間:2022-02-25 10:32:34 來源:億速云 閱讀:260 作者:iii 欄目:開發技術

這篇文章主要介紹了StringBuilder是線程不安全的原因是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇StringBuilder是線程不安全的原因是什么文章都會有所收獲,下面我們一起來看看吧。

原因分析

如果你看了 StringBuilderStringBuffer 的源代碼會說,因為StringBuilderappend操作時并未使用線程同步,而StringBuffer幾乎大部分方法都使用了synchronized關鍵字進行方法級別的同步處理。

上面這種說法肯定是正確的,對照一下StringBuilderStringBuffer的部分源代碼也能夠看出來。

StringBuilderappend方法源代碼:

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

StringBufferappend方法源代碼:

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

對于上面的結論肯定是沒什么問題的,但并沒有解釋是什么原因導致了StringBuilder的線程不安全?為什么要使用synchronized來保證線程安全?如果不是用會出現什么異常情況?

下面我們來逐一講解。

異常示例

我們先來跑一段代碼示例,看看出現的結果是否與我們的預期一致。

@Test
public 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();
  }
  // 睡眠確保所有線程都執行完
  Thread.sleep(1000);
  System.out.println(sb.length());
}

上述業務邏輯比較簡單,就是構建一個StringBuilder,然后創建10個線程,每個線程中拼接字符串“a”1000次,理論上當線程執行完成之后,打印的結果應該是10000才對。

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

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中針對字符串的處理主要依賴兩個成員變量char數組valuecountStringBuilder通過對value的不斷擴容和count對應的增加來完成字符串的append操作。

// 存儲的字符串(通常情況一部分為字符串內容,一部分為默認值)
char[] value;


// 數組已經使用數量
int count;

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

如果查看構造方法我們會發現,在創建StringBuilder時會設置數組value的初始化長度。

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

默認是傳入字符串長度加16。這就是count存在的意義,因為數組中的一部分內容為默認值。

當調用append方法時會對count進行增加,增加值便是append的字符串的長度,具體實現也在抽象父類中。

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;
}

我們所說的線程不安全的發生點便是在append方法中count的“+=”操作。我們知道該操作是線程不安全的,那么便會發生兩個線程同時讀取到count值為5,執行加1操作之后,都變成6,而不是預期的7。這種情況一旦發生便不會出現預期的結果。

拋異常的原因

回頭看異常的堆棧信息,回發現有這么一行內容:

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

對應的代碼就是上面AbstractStringBuilderappend方法中的代碼。對應方法的源代碼如下:

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);
}

其實異常是最后一行arraycopyJVM底層發生的。arraycopy的核心操作就是將傳入的String對象copyvalue當中。

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

那么,為什么會超出這么一個位置呢?這與我們上面講到到的count被少加有關。在執行str.getChars方法之前還需要根據count校驗一下當前的value是否使用完畢,如果使用完了,那么就進行擴容。append中對應的方法如下:

ensureCapacityInternal(count + len);

ensureCapacityInternal的具體實現:

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

count本應該為7,value長度為6,本應該觸發擴容。但因為并發導致count為6,假設len為1,則傳遞的minimumCapacity為7,并不會進行擴容操作。這就導致后面執行str.getChars方法進行復制操作時訪問了不存在的位置,因此拋出異常。

這里我們順便看一下擴容方法中的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;
}

除了校驗部分,最核心的就是將新數組的長度擴充為原來的兩倍再加2。把計算所得的新長度作為Arrays.copyOf的參數進行擴容。

關于“StringBuilder是線程不安全的原因是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“StringBuilder是線程不安全的原因是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

申扎县| 阿巴嘎旗| 建昌县| 泽州县| 台中市| 成都市| 漳平市| 海原县| 团风县| 泸水县| 方正县| 微山县| 麻江县| 马龙县| 奇台县| 彰武县| 台州市| 寿宁县| 莲花县| 寿阳县| 突泉县| 时尚| 平利县| 蓝田县| 遵义市| 伊吾县| 当雄县| 全州县| 固镇县| 固原市| 建始县| 澜沧| 新干县| 仲巴县| 珲春市| 会宁县| 冕宁县| 邯郸市| 镇江市| 鲁山县| 浏阳市|