私が求めている質問は 、StringBuilder と StringBuffer の違いに関連していますが、同じではありません。StringBuilder が 2 つのスレッドによって同時に変更された場合に実際に何が起こるかを確認したいと考えています。
私は次のクラスを書きました:
public class ThreadTester
{
public static void main(String[] args) throws InterruptedException
{
Runnable threadJob = new MyRunnable();
Thread myThread = new Thread(threadJob);
myThread.start();
for (int i = 0; i < 100; i++)
{
Thread.sleep(10);
StringContainer.addToSb("a");
}
System.out.println("1: " + StringContainer.getSb());
System.out.println("1 length: " + StringContainer.getSb().length());
}
}
public class MyRunnable implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
StringContainer.addToSb("b");
}
System.out.println("2: " + StringContainer.getSb());
System.out.println("2 length: " + StringContainer.getSb().length());
}
}
public class StringContainer
{
private static final StringBuffer sb = new StringBuffer();
public static StringBuffer getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
最初は、StringContainer に StringBuffer を保持していました。StringBuffer はスレッドセーフであるため、一度に 1 つのスレッドしか追加できないため、出力は一貫しています。どちらのスレッドも、次のようにバッファの長さを 200 と報告しています。
1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
1 length: 200
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
2 length: 200
または、そのうちの 1 人が 199 を報告し、他の 200 人が次のように報告しました。
2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab
2 length: 199
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa
1 length: 200
重要なのは、完了する最後のスレッドが 200 の長さを報告することです。
ここで、StringContainer を StringBuffer の代わりに StringBuilder を持つように変更しました。
public class StringContainer
{
private static final StringBuilder sb = new StringBuilder();
public static StringBuilder getSb()
{
return sb;
}
public static void addToSb(String s)
{
sb.append(s);
}
}
いくつかの書き込みが上書きされることを期待していますが、それは起こっています。ただし、StringBuilder の内容と長さが一致しない場合があります。
1: ababbabababaababbaabbabababababaab
1 length: 137
2: ababbabababaababbaabbabababababaab
2 length: 137
ご覧のとおり、印刷されたコンテンツには 34 文字しかありませんが、長さは 137 です。なぜこれが起こっているのでしょうか?
@Extreme Coders - もう 1 回テストを実行しました。
2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150
Java バージョン: 1.6.0_45 で、Eclipse バージョン: Eclipse Java EE IDE for Web Developers を使用しています。バージョン: Juno サービス リリース 2 ビルド ID: 20130225-0426
更新 1:これを Eclipse の外で実行したところ、一致しているように見えますが、時々 ArrayIndexOutOfBoundsException が発生しています:
$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK Server VM (build 20.0-b12, mixed mode)
$ java ThreadTester
1: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
1 length: 123
2: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
2 length: 123
$ java ThreadTester
2: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
2 length: 115
1: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
1 length: 115
$ java ThreadTester
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:862)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:408)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at StringContainer.addToSb(StringContainer.java:14)
at ThreadTester.main(ThreadTester.java:14)
2: abbbbbbababbbbabbbbababbbbaabbabbbaaabbbababbbbabaabaabaabaaabababaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 length: 114
ArrayIndexOutOfBoundsException は、Eclipse から実行しているときにも発生しています。
更新 2: 2 つの問題が発生しています。StringBuilder の内容が長さと一致しないという最初の問題は、コマンドラインで実行したときではなく、Eclipse でのみ発生しています (コマンドラインで実行した少なくとも 100 回以上は発生しませんでした)。
Arrays.copyOf
ArrayIndexOutOfBoundsException の 2 番目の問題は、文字の配列を保持し、サイズを拡張するときに行う StringBuilder クラスの内部実装に関係するはずです。しかし、実行順序に関係なく、サイズが拡張される前に書き込みがどのように行われるかはまだわかりません。
ところで、私は@GreyBeardedGeekのこの演習全体が時間の無駄であるという答えに同意する傾向があります:-)。ときどき、コードの出力などの症状だけを見て、何が問題なのか疑問に思うことがあります。この質問は、 2 つのスレッドが (非常によく知られている) スレッドの安全でないオブジェクトを変更していることを先験的に宣言しました。
更新 3: Java Concurrency in Practice pからの公式の回答は次のとおりです。35:
同期がない場合、コンパイラ、プロセッサ、およびランタイムは、操作が実行されているように見える順序に対して、まったく奇妙なことを行う可能性があります。十分に同期されていないマルチスレッド プログラムでメモリ アクションが発生する「必要がある」順序について推論しようとする試みは、ほぼ確実に正しくありません。
同時実行プログラムの同期が不十分であるという理由付けは非常に困難です。
NoVisibility
pの本にも良い例があります。34.