0

それらの違いを視覚的に確認したいので、以下にコードを示します。しかし、それは常に失敗します。誰かがこれについて私を助けてくれますか?SOに関する質問も見ましたが、プログラムで違いを示したものはありません。

public class BBDifferencetest {
     protected static int testnum = 0;

     public static void testStringBuilder() {
             final StringBuilder sb = new StringBuilder();

             Thread t1 = new Thread() {

                     @Override
                     public void run() {
                             for (int x = 0; x < 100; x++) {
                                     testnum++;
                                     sb.append(testnum);
                                     sb.append(" ");                                
                             }
                     }
             };
             Thread t2 = new Thread() {

                     public void run() {

                             for (int x = 0; x < 100; x++) {
                                     testnum++;
                                     sb.append(testnum);
                                     sb.append(" ");
                             }
                     }
             };
             t1.start();
             t2.start();

             try {
                     t1.join();
                     t2.join();
             } catch (InterruptedException e) {
                     e.printStackTrace();
             }

             System.out.println("Result is: " + sb.toString());

        }

     public static void main(String args[]) {
             testStringBuilder();
     }
}

これを実行すると、ランダムに出力が得られることがあるので、これは私のテストを証明しています。しかし、私が置き換えStringBuilderStringBufferテストするときでさえ、それでさえ私に予期しない出力を与えます(1から200までのシーケンシャルではありません)。それで、誰かが私が違いを視覚的に知るのを手伝ってくれるでしょうか?

PS:誰かが違いを示すあなたのコードを持っているなら、私はそれを答えとして受け入れてとてもうれしいです。コードを変更しても、コードとの違いを実現できるかどうかわからないためです。

4

3 に答える 3

3

(1 から 200 までの順次ではなく)

各スレッドは、 に対して読み取り、変更、書き込み操作を実行していtestnumます。それ自体はスレッドセーフではありません。

次に、各スレッドは、値testnumを追加するために値を再度フェッチします。他のスレッドがそれまでに割り込んで、値を再度インクリメントした可能性があります。

コードを次のように変更した場合:

AtomicInteger counter = new AtomicInteger();
...
sb.append(counter.getAndIncrement());

そうすれば、期待どおりの結果が得られる可能性が高くなります。

appendわかりやすくするために、次のように、ループを1 回だけ呼び出すように変更します。

for (int x = 0; x < 100; x++) {
    sb.append(counter.incrementAndGet() + " ");
}

私がそうするとき、StringBuffer私は常に「完璧な」出力を得るからです。StringBuilder私は時々次のような出力を得るからです:

97 98 100    102     104

ここでは、2 つのスレッドが両方とも同時に追加されており、内容が台無しになっています。

編集:これはやや短い完全な例です:

import java.util.concurrent.atomic.AtomicInteger;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        final AtomicInteger counter = new AtomicInteger();
        // Change to StringBuffer to see "working" output
        final StringBuilder sb = new StringBuilder();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int x = 0; x < 100; x++) {
                    sb.append(counter.incrementAndGet() + " ");
                }
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sb);
    }
}
于 2013-03-08T04:47:20.570 に答える
1

StringBuffer はメソッド レベルで同期されます。これは、スレッドが既にメソッドの 1 つにある場合、誰もメソッドの 1 つに入ることができないことを意味します。ただし、一方のスレッドが StringBuilder を使用している限り、もう一方のスレッドが StringBuilder を使用することをまったくブロックされるとは限りません。そのため、2 つのスレッドはメソッドへのアクセスをめぐって競合し、順序付けられていない結果がランダムに発生する可能性があります。

StringBuffer へのアクセスを実際にロックする唯一の方法は、同期ブロックにアクセスするコードを配置することです。

public void run() {
    synchronized(sb) {
        for (int x = 0; x < 100; x++) {
           testnum++;
           sb.append(testnum);
           sb.append(" ");                                
        }
    }
}

そうしないと、スレッド 1 は sb.append(testnum) に入ることができ、スレッド 2 はそのエントリで待機します。スレッド 1 が終了すると、スレッド 2 は内部に入り、スレッドの前に書き込みを開始する可能性があります。 1 は sb.append(" ") に入ります。したがって、次のように表示されます。

12 13 1415  16 ....

問題は、このようにロックすると、StringBuilder でも機能するようになるということです。そのため、StringBuffer の同期メカニズムはまったく役に立たないと言えます。そのため、もはや使用されていません (Vector についても同様です)。

したがって、この方法では、StringBuilder と StringBuffer の違いを示すことはできません。Jon Skeetの回答の提案の方が優れています。

于 2013-03-08T04:53:01.113 に答える
0

シリルが言ったことに+1。本質的にアトミック型 (プリミティブ <= 32 ビット) の配列の性質だけが、たとえばList<Integer>

基本的に、2 つのスレッドがあり、それぞれ 100 の個別の操作があります。この 2 つは、各追加の前にオブジェクトのロックをめぐって競合し、その後、それぞれ 100 回ずつオブジェクトを解放します。各反復で勝つスレッドは、ループ カウンターと testnum をインクリメントするのにかかる (非常に) 短い時間によってランダム化されます。

あなたの例との違いのより典型的なものは、必ずしも順序付けではありませんが、StringBuilder を使用するときにすべての挿入が実際に考慮されるようにすることです。内部同期がないため、その過程で一部が変更または上書きされる可能性は十分にあります。StringBuffer は、すべての挿入が適切に行われることを保証する内部同期でこれを処理しますが、StringBuilder を安全に使用するために各スレッドの反復シーケンス全体のロックを保持するには、上記の Cyrille の例のような外部同期が必要です。

于 2013-03-08T05:17:38.323 に答える