1

スレッド干渉シナリオを再現しようとしていますが、何かが正しくありません。私が何を理解するのを手伝ってください

主要

public static void main(String args[]) throws InterruptedException {

    Counter c = new Counter();

    for (int i = 0; i < 1000000; i++) {
        new T1(c).start();
        new T2(c).start();
    }

    System.out.println(c.value()); // <-- Expect this to sometimes not be 0
}

カウンター

class Counter {
    private int c   = 0;

    public void increment() { // <-- intentionally not synchronized 
        c++;
    }

    public void decrement() { // <-- intentionally not synchronized 
        c--;
    }

    public int value() {
        return c;
    }
}

スレッド1

public class T1 extends Thread {

    Counter c;

    T1(Counter c) {
        this.c = c;
    }

    public void start() {
        c.decrement(); // <-- Decrement
    }
}

スレッド2

public class T2 extends Thread {

    Counter c;

    T2(Counter c) {
        this.c = c;
    }

    public void start() {
        c.increment();  // <-- Increment
    }
}

それぞれがコードの同期されていないセクションで動作している1000000スレッドを開始しているので、いくつかの操作が重複すると予想されます。

干渉は、異なるスレッドで実行されているが同じデータに作用する2つの操作がインターリーブするときに発生します。これは、2つの操作が複数のステップで構成されており、ステップのシーケンスが重複していることを意味します。

cに対する両方の操作は単一の単純なステートメントであるため、Counterのインスタンスに対する操作がインターリーブできないように見える場合があります。ただし、単純なステートメントでも、仮想マシンによって複数のステップに変換される可能性があります。仮想マシンが実行する特定の手順については説明しません。単一の式c++を3つの手順に分解できることを知っていれば十分です。

cの現在の値を取得します。取得した値を1ずつインクリメントします。インクリメントした値をcに戻します。

何が足りないのですか?

4

2 に答える 2

4

現状では、プログラムは実際には同時に何も実行していないため、すべてがメインスレッドで実行されています。startメソッドをオーバーライドし、スーパークラスバージョンを呼び出さないため、新しいスレッドを実行するための機能を取得することはできません。

ただし、super.start()を呼び出すだけでは問題は解決しません。startは現在のスレッドによって実行されると考えてください。したがって、スレッドを開始できたとしても、インクリメント/デクリメントはstart()を呼び出すスレッドによって実行され、同時アクセスと変更は行われません。

インクリメント/デクリメントをstartメソッドから移動し(オーバーライドされたバージョンを削除し)、runメソッドをオーバーライドして、代わりにそこに配置します。もちろん、次回はThreadの代わりにRunnableを使用することを検討してください。Runnableを使用すると、この種のエラーを発生させるのが難しくなります。

于 2012-07-08T01:53:21.063 に答える
3

スレッドを開始しないという事実から始めて、プログラムには多くの問題があります。スレッドの場合、スレッドの本体はrun()メソッドに含まれている必要があります。Threadクラスのstart()メソッドはオーバーライドしてはなりません。

次に、作成するスレッドの数に大きな問題が発生します:2000000。これは、それらを実行するための2000000スタックを意味します。デフォルトのスタックサイズ(-Xssオプションで指定しない限り)は512Kbから2Mbの間です。128Kbの低いスタックサイズを使用する場合でも、これらの2000000スレッドを作成するには256GBのメモリが必要です。スレッドがそれほど短命でなければ、それだけのメモリが必要になります。それらは1つの作業命令のみを実行してから停止するため、おそらく、それらの多くを開始する前に、それらのほとんどが終了します。または、十分なメモリまたはリソースの例外が発生してプロセスがクラッシュします。最後に、ほとんどの場合、スレッドの初期化と終了になります。スレッドで使用されている実際のCPUの1%未満が、カウンターの実際のインクリメントまたはデクリメントに使用されていると推定されます。

本当にオーバーラップを確認したい場合は、作成するスレッドを大幅に減らしたいと思います(IMO 16以下。とにかく、同時に実行できるスレッドの数、つまりマシンのプロセッサコアの数)が、これらのスレッドは増分を実行する必要があります。ループ内の/decrements。

つまり、2つのタイプのスレッドです。1つのタイプはカウンターで200000インクリメントし、もう1つのタイプは200000デクリメントします。各タイプの8つのスレッドを開始します。Thread.join()を使用して16スレッドすべてが終了するのを待ってから、カウンターの結果を書き込みます。

于 2012-07-08T03:04:51.543 に答える