1
class Counter
{
    public int i=0;
    public void increment()
    {
        i++;
        System.out.println("i is "+i);
        System.out.println("i/=2 executing");
        i=i+22;
        System.out.println("i is (after i+22) "+i);
        System.out.println("i+=1 executing");
        i++;
        System.out.println("i is (after i++) "+i);
    }
    public void decrement()
    {
        i--;
        System.out.println("i is "+i);
        System.out.println("i*=2 executing");
        i=i*2;
        System.out.println("i is after i*2"+i);
        System.out.println("i-=1 executing");
        i=i-1;
        System.out.println("i is after i-1 "+i);
    }
    public int value()
    {
        return i;
    } }

class ThreadA
{
    public ThreadA(final Counter c)
    {
        new Thread(new Runnable(){
            public void run()
            {
                System.out.println("Thread A trying to increment");
                c.increment();
                System.out.println("Increment completed "+c.i);
            }
        }).start();
    }
}
class ThreadB
{
    public ThreadB(final Counter c)
    {
        new Thread(new Runnable(){
            public void run()
            {
                System.out.println("Thread B trying to decrement");
                c.decrement();
                System.out.println("Decrement completed "+c.i);
            }
        }).start();
    }
}
class ThreadInterference
{
    public static void main(String args[]) throws Exception
    {
        Counter c=new Counter();
        new ThreadA(c);
        new ThreadB(c); 
    }
}

上記のコードでは、ThreadA は最初に Counter オブジェクトにアクセスし、いくつかの追加操作を実行するとともに値をインクリメントしました。初めて ThreadA にキャッシュされた i の値がありません。ただし、i++ の実行後 (最初の行)、値がキャッシュされます。その後、値が更新されて 24 になります。プログラムによると、変数 i は揮発性ではないため、変更は ThreadA のローカル キャッシュで行われます。

ThreadB が decrement() メソッドにアクセスすると、i の値は ThreadA、つまり 24 によって更新されます。

4

4 に答える 4

5

他のスレッドが共有データに対して行った各更新をスレッドが認識しないと仮定することは、すべてのスレッド互いの更新をすぐに認識できると仮定するのと同じくらい不適切です。

重要なことは、更新が表示されない可能性を考慮に入れることです。更新に依存するのではありません。

他のスレッドからの更新が表示されない以外に、別の問題があります。すべての操作は「読み取り、変更、書き込み」の意味で機能します...読み取り後に別のスレッドが値を変更すると、基本的にそれを無視します。

たとえば、i次の行に到達したときに が 5であるとします。

i = i * 2;

...しかし、途中で、別のスレッドがそれを 4 に変更します。

その行は次のように考えることができます。

int tmp = i;
tmp = tmp * 2;
i = tmp;

「拡張」バージョンの最初の行の後に 2 番目のスレッドがi4 に変更された場合、たとえivolatile であっても、4 の書き込みは事実上失われtmpます。 10が書き出されます。

于 2013-03-26T20:23:18.807 に答える
0

で指定されているJLS 8.3.1.4とおり:

Java プログラミング言語では、スレッドが共有変数にアクセスできます (§17.1)。原則として、共有変数が一貫して確実に更新されるようにするために、スレッドは、慣習的にそれらの共有変数の相互排除を強制するロックを取得することによって、そのような変数を排他的に使用できるようにする必要があります....フィールドは volatile と宣言される場合があります。その場合、Java メモリ モデルにより、すべてのスレッドが変数の一貫した値を参照できるようになります。

常にではありませんが、スレッド間の共有値が一貫して確実に更新されない可能性があり、プログラムの予期しない結果につながる可能性があります。以下のコードで

class Test {
    static int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

あるスレッドがメソッド 1 を繰り返し呼び出し (ただし、全部で Integer.MAX_VALUE 回を超えない)、別のスレッドがメソッド 2 を繰り返し呼び出す場合、メソッド 2 は i の値よりも大きい j の値を時折出力する可能性があります。この例には同期が含まれておらず、i と j の共有値が順不同で更新される可能性があります。しかし、 and を と宣言すると、
これにより、メソッド 1 とメソッド 2 を同時に実行できますが、 と の共有値へのアクセスが、実行中に発生するように見えるのとまったく同じ回数、まったく同じ順序で発生することが保証されます。各スレッドによるプログラムテキストの。したがって、の共有価値が の共有価値よりも大きくなることはありません。ijvolatileijjij への更新が発生する前に、i への各更新が i の共有値に反映される必要があるためです。

于 2013-03-26T20:44:19.487 に答える
0

それはどのように可能でしょうか?

JLS には、値をスレッド内にキャッシュする必要があるとはどこにも記載されていないためです。

これは仕様が言うことです:

不揮発性変数xがあり、それがスレッドによって更新された場合、 がbyの変更を監視できるT1保証はありません。の変化を見ることを保証する唯一の方法は、関係を持つことです。T2xT1T2T1happens-before

Java の一部の実装では、特定のケースで非揮発性変数をスレッド内にキャッシュすることがあります。つまり、キャッシュされている不揮発性変数に依存することはできません。

于 2013-07-18T14:39:58.863 に答える
0

ここで、共通オブジェクト (複数のスレッドによって共有されているオブジェクト) がそれらのスレッドによってキャッシュされていないことを知りました。オブジェクトは共通であるため、Java メモリ モデルは、共通オブジェクトがスレッドによってキャッシュされたときに驚くべき結果をもたらす可能性があることを十分に識別できます。

于 2013-03-27T18:16:43.820 に答える