StringBuilderがスレッドセーフではないのはなぜですか?



Why Is Stringbuilder Not Thread Safe



著者:銭山

Juejin.im/post/5d6228046fb9a06add4e37fe



前書き

インタビュアー: StringBuilderとStringBufferの違いは何ですか?

私:StringBuilderはスレッドセーフではありません、StringBufferはスレッドセーフです



インタビュアー: StringBuilderの不安はどこにありますか?

私:。 。 。 (ダム)

その前は、StringBuilderはスレッドセーフではなく、StringBufferはスレッドセーフであるという結論を思い出しただけでした。 StringBuilderが安全でない理由については、私は考えたことはありませんでした。



分析

この問題を分析する前に、StringBuilderとStringBufferの内部実装が、char配列を介して文字列を格納するStringクラスと同じであることを知っておく必要があります。違いは、Stringクラスのchar配列が最終的に変更され、不変であるということです。 StringBuilderとStringBufferのchar配列は可変です。

まず、コードの一部を見て、マルチスレッドのStringBuilderオブジェクトでどのような問題が発生するかを確認しましょう。

public class StringBuilderDemo { public static void main(String[] args) throws InterruptedException { StringBuilder stringBuilder = new StringBuilder() for (int i = 0 i <10 i++){ new Thread(new Runnable() { @Override public void run() { for (int j = 0 j < 1000 j++){ stringBuilder.append('a') } } }).start() } Thread.sleep(100) System.out.println(stringBuilder.length()) } }

このコードは10個のスレッドを作成し、各スレッドは1000回ループして、StringBuilderオブジェクトに文字を追加していることがわかります。通常の状況では、コードは10000を出力するはずですが、実際の出力はどうなりますか?

StringBuilder?

予想される10000未満の「9326」が出力され、ArrayIndexOutOfBoundsExceptionもスローされることがわかります(例外は必須ではありません)。

1.出力値が期待値と異なる理由

StringBuilderの2つのメンバー変数を見てみましょう(これらの2つのメンバー変数は実際にはAbstractStringBuilderで定義されており、StringBuilderとStringBufferの両方がAbstractStringBuilderを継承しています)

//The specific content of the storage string char[] value //Number of character arrays already used int count

StringBuilderのappend()メソッドを見てください。

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

StringBuilderのappend()メソッドによって呼び出される親クラスAbstractStringBuilderの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 }

最初にコードの5行目と6行目を無視しましょう。 7行目を直接見ると、count + = lenはアトミック操作ではありません。このとき、カウント値が10、len値が1であると仮定します。両方のスレッドが7行目を同時に実行します。得られたカウント値は10です。加算演算が実行された後、結果がカウントに割り当てられるため、2つのスレッドが実行された後、カウント値は12ではなく11になります。これがテストコードの出力値が10000。

2.ArrayIndexOutOfBoundsExceptionがスローされる理由。

AbstractStringBuilderのappend()メソッドのソースコードの5行目を振り返ってみましょう。 sureCapacityInternal()メソッドは、StringBuilderオブジェクトの元のchar配列容量が新しい文字列を保持できるかどうかをチェックします。持ちこたえられない場合は、char配列でexpandCapacity()メソッドを呼び出します。拡張。

private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity) }

容量拡張のロジックは新しい新しいchar配列であり、新しいchar配列の容量は元のchar配列の2倍に2を加えたものであり、元の配列の内容はSystem.arryCopy( )関数であり、ポインタは最終的に新しいchar配列を指します。

void expandCapacity(int minimumCapacity) { //Calculate new capacity int newCapacity = value.length * 2 + 2 // Some inspection logic is omitted in the middle ... value = Arrays.copyOf(value, newCapacity) }

Arrys.copyOf()メソッド

public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength] // copy array System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)) return copy }

AbstractStringBuilderのappend()メソッドのソースコードの6行目は、Stringオブジェクトのchar配列の内容をStringBuilderオブジェクトのchar配列にコピーすることです。コードは次のとおりです。

str.getChars(0, len, value, count)

getChars()メソッド

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { ///Some checks are omitted in the middle ... System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin) }

コピープロセスを以下に示します。

StringBuilder?

StringBuilderのappend()メソッドを同時に実行している2つのスレッドがあり、両方のスレッドが5行目のensureCapacityInternal()メソッドを実行しているとします(現時点ではcount = 5)。

StringBuilder?

このとき、スレッド1のCPUタイムスライスは使い果たされ、スレッド2は実行を継続します。スレッド2がappend()メソッド全体を実行した後、カウントは6になります。

StringBuilder?

スレッド1が6行目でstr.getChars()メソッドを実行し続けると、取得されるカウント値は6になり、char配列をコピーするときにArrayIndexOutOfBoundsException例外がスローされます。

この時点で、StringBuilderが安全でない理由が分析されました。テストコードのStringBuilderオブジェクトをStringBufferオブジェクトに置き換えるとどうなりますか?

StringBuilder?

もちろん出力は10000!

では、StringBufferはスレッドセーフを確保するためにどのような意味を使用しますか? StringBufferのappend()メソッドをクリックすると、この問題を知ることができます。

推奨読書(クリックしてジャンプして読む)

1.1。 SpringBootコンテンツ集約

二。 インタビュー質問コンテンツ集約

3.3。 デザインパターンコンテンツの集約

四。 Mybatisコンテンツの集約

5.5。 マルチスレッドコンテンツの集約

Javaç&yené³å®æ&sup1å&notä&frac14å・