1

端的に言えば、同期メソッドを使用して、Javaで並行性をテストするコードを作成しました。

コード:

public class ThreadTraining {

    public static class Value {

        private static int value;

        public static synchronized void Add() {
            value++;
        }

        public static synchronized void Sub() {
            value--;
        }

        public static synchronized int Get() {
            return value;
        }
    }

    public static class AddT implements Runnable {

        public static String name;

        public AddT(String n) {
            name = n;
        }

        @Override
        public void run() {
            while (Value.Get() < 100) {
                int prev = Value.Get();
                Value.Add();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
        }
    }

    public static class SubT implements Runnable {

        public static String name;

        public SubT(String n) {
            name = n;
        }

        @Override
        public void run() {
            while (Value.Get() > (-100)) {
                int prev = Value.Get();
                Value.Sub();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[3];
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                threads[i] = new Thread(new AddT("Adder - Thread #" + i));
            } else {
                threads[i] = new Thread(new SubT("Subtractor - Thread #" + i));
            }
            threads[i].start();
        }
    }
}

このコードは、 「同じオブジェクトで同期されたメソッドを2回呼び出してインターリーブすることはできない」という事実にもかかわらず、実行の信頼性が低くなっています。(出典:Oracleの並行性チュートリアル-同期メソッド)、次のような信頼性の低い出力を提供します:(出力のパターン化されていない変更は、信頼性の低い動作だけでなく、「...」行の間に示されます)

-----[Thread #0 - Adder] has been created!
=====[Thread #0 - Adder] has been started!
-----[Thread #1 - Subtractor] has been created!
[Thread #0 - Adder] - changed value from 0 to 1
[Thread #0 - Adder] - changed value from 1 to 2
[Thread #0 - Adder] - changed value from 2 to 3
...\*goes on, adding as expected, for some lines*\
[Thread #0 - Adder] - changed value from 83 to 84
[Thread #0 - Adder] - changed value from 84 to 85
-----[Thread #2 - Adder] has been created!
=====[Thread #1 - Subtractor] has been started!
[Thread #0 - Adder] - changed value from 85 to 86
[Thread #1 - Subtractor] - changed value from 86 to 85
[Thread #1 - Subtractor] - changed value from 86 to 85
[Thread #1 - Subtractor] - changed value from 85 to 84
...\*goes on, subtracting as expected, for some lines*\
[Thread #1 - Subtractor] - changed value from -98 to -99
[Thread #1 - Subtractor] - changed value from -99 to -100 \*This thread ends here, as it reaches the state where (value>(-100))==false*\
=====[Thread #2 - Adder] has been started!
[Thread #2 - Adder] - changed value from -100 to -99
[Thread #2 - Adder] - changed value from -99 to -98
[Thread #2 - Adder] - changed value from -98 to -97
...\*goes on as expected...*\
[Thread #2 - Adder] - changed value from -67 to -66
[Thread #2 - Adder] - changed value from -66 to -65
-----[Thread #3 - Subtractor] has been created!
=====[Thread #3 - Subtractor] has been started!
[Thread #3 - Subtractor] - changed value from -65 to -66
[Thread #3 - Subtractor] - changed value from -66 to -67
...\*goes on as expected...*\
[Thread #3 - Subtractor] - changed value from -71 to -72
[Thread #3 - Subtractor] - changed value from -72 to -73 \*NOTE: From -73 it goes to -74, without a Subtractor-action in between! WTF???*\
[Thread #2 - Adder] - changed value from -74 to -73
[Thread #2 - Adder] - changed value from -73 to -72
...\*goes on as expected...*\
[Thread #2 - Adder] - changed value from 98 to 99
[Thread #2 - Adder] - changed value from 99 to 100 \*This adder ends here, adder thread #0 probably ends after next line...but not before doing something crazy!*\
[Thread #0 - Adder] - changed value from 85 to 86 \*What the hell are these values doing here? Oh wait, next lines is...*\
[Thread #3 - Subtractor] - changed value from -73 to -47\*...Holy mother of god!*\
[Thread #3 - Subtractor] - changed value from 100 to 99
[Thread #3 - Subtractor] - changed value from 99 to 98
...
[Thread #3 - Subtractor] - changed value from -98 to -99
[Thread #3 - Subtractor] - changed value from -99 to -100 \*The logical nightmare is finally over.*\

同期されたメソッドの使用は信頼できませんか?それとも実装が間違っていますか?(もしそうなら、それでは何が悪いのですか?そしてそれを修正する方法は?)

4

4 に答える 4

3

あなたの実装は少しずれています。「Get」、「Add」、「Sub」はそれぞれロックされていますが、「Get」と加算または減算の間にギャップがあります。'Get'を実行したばかりのスレッドは、そのギャップの間に休憩を取ることができ、他の誰かが値を変更します。複数のメソッド呼び出しをすべて1つの操作として実行する場合は、個々のメソッドよりも「大きい」もので同期する必要があります。

synchronized (Value.class) {
  int prev = Value.Get();
  Value.Add();
  System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
}

これには、入力時に100未満がまだ当てはまらない可能性があるという問題がまだあることに注意してください。したがって、再確認する必要があります。(もちろん、Classオブジェクトのロックは、「実際の」コードで一般的に実行したいことではありません:))

于 2012-05-15T17:51:10.933 に答える
2

あなたが見ているのは「信頼できない」ものでも、実装上の問題でもありません。多数の競合状態を処理しています。例えば:

while (Value.Get() < 100) {
     // other threads could have called `Add()` or `Subtract()` here
     int prev = Value.Get();
     // other threads could have called `Add()` or `Subtract()` here
     Value.Add();
     // other threads could have called `Add()` or `Subtract()` here
     System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
}

このループで3回呼び出すGet()と、他のスレッドがへの各呼び出しの間に値を変更したという保証はありませんGet()。コードを次のように変更すると、より一貫性のある出力が生成されます。

while (true) { {
     // only call get once
     int prev = Value.Get();
     if (prev >= 100) {
        break;
     }
     // return the new value from add
     int newValue = Value.Add();
     System.out.println("[" + name + "] - changed value from " + prev + " to " + newValue);
}

// here's the new version of add
public static synchronized int Add() {
    value++;
    return value;
}

Get()これはテストであり、次にセットであるため、とAdd()メソッド呼び出しの間にはまだ競合状態があることに注意してください。

于 2012-05-15T17:50:10.910 に答える
2

コードの何が問題になっているのかを理解するには、次のことを考慮してください。

int prev = Value.Get();
Value.Sub();
System.out.println(... + Value.Get());

3つのsynchronized操作Get()のそれぞれはSub()Get()正しくアトミックに動作することが保証されています。ただし、これらの操作と変更の間に別のスレッドが入らないという保証はありませんValue

一連の操作のアトミック性を確保する必要がある場合は常に、それらすべてを一度に実行する単一のメソッドを提供するか、次のようなsynchronized外部ブロックを使用する必要があります。synchronized

synchronized (Value.class) {
    int prev = Value.Get();
    Value.Sub();
    System.out.println(... + Value.Get());
}
于 2012-05-15T17:50:54.480 に答える
1

これが完全に実行されるコードです。ここで@Affeと@Aixによって与えられた答えを補完します。

public class ThreadTraining {

    public static class Value {

        private static  int value;

        public static synchronized void Add() {
            value++;

        }

        public static synchronized void Sub() {
            value--;
        }

        public static synchronized int Get() {
            return value;
        }
    }

    public static class AddT implements Runnable {

        public static String name;

        public AddT(String n) {
            name = n;
        }

        @Override
        public void run() {

            while (Value.Get() < 100) {
                 synchronized(Value.class){
                int prev = Value.Get();
                Value.Add();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
            }
        }
    }

    public static class SubT implements Runnable {

        public static String name;

        public SubT(String n) {
            name = n;
        }

        @Override
        public void run() {

            while (Value.Get() > (-100)) {
            synchronized(Value.class){
                int prev = Value.Get();
                Value.Sub();
                System.out.println("[" + name + "] - changed value from " + prev + " to " + Value.Get());
            }
            }
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[3];
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                threads[i] = new Thread(new AddT("Adder - Thread #" + i));
            } else {
                threads[i] = new Thread(new SubT("Subtractor - Thread #" + i));
            }
            threads[i].start();
        }
    }
}

変更されたコードに示されているように同期ブロックを配置しないことにより、操作をアトミックに完了せずにスレッドに変数値を取得させることができます。したがって、結果として生じる不整合。

于 2012-05-15T18:35:09.137 に答える