web-dev-qa-db-ja.com

なぜこのクラスはスレッドセーフではないのですか?

class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

上記のクラスがスレッドセーフではない理由を誰でも説明できますか?

94
das kinder

incrementメソッドはstaticであるため、ThreadSafeClassのクラスオブジェクトで同期します。 decrementメソッドは静的ではなく、呼び出しに使用されたインスタンスで同期します。つまり、異なるオブジェクトで同期するため、2つの異なるスレッドが同時にメソッドを実行できます。 ++および--操作はアトミックではなく、クラスはスレッドセーフではありません。

また、countstaticであるため、同期instanceメソッドであるdecrementから変更すると、異なるインスタンスで呼び出すことができ、 countを同時にそのように変更します。

134
K Erlandsson

2つの同期メソッドがありますが、一方は静的で、もう一方はそうではありません。タイプ(静的または非静的)に基づいて同期メソッドにアクセスすると、別のオブジェクトがロックされます。静的メソッドの場合、Classオブジェクトにロックがかけられ、非静的ブロックの場合、メソッドを実行するクラスのインスタンスにロックがかけられます。 2つの異なるロックされたオブジェクトがあるため、同じオブジェクトを同時に変更する2つのスレッドを持つことができます。

23
Slimu

上記のクラスがスレッドセーフではない理由を誰でも説明できますか?

  • incrementは静的であるため、同期はクラス自体で行われます。
  • decrementは静的ではないため、オブジェクトのインスタンス化で同期が行われますが、countは静的であるため、何も保護されません。

それを追加して、スレッドセーフカウンターを宣言します。最も簡単な方法は、プリミティブなintの代わりに AtomicInteger を使用することです。

Java.util.concurrent.atomic package-info。

14

他の人の答えはその理由を説明するのにかなり良いです。要約を追加するだけでsynchronized

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

fun1およびfun2上のsynchronizedは、インスタンスオブジェクトレベルで同期されます。 fun4synchronizedは、クラスオブジェクトレベルで同期されます。つまり:

  1. 2つのスレッドがa1.fun1()を同時に呼び出すと、後者の呼び出しはブロックされます。
  2. スレッド1がa1.fun1()を呼び出し、スレッド2がa1.fun2()を同時に呼び出すと、後者の呼び出しはブロックされます。
  3. スレッド1がa1.fun1()を呼び出し、スレッド2がa1.fun3()を同時に呼び出した場合、ブロッキングは発生せず、2つのメソッドが同時に実行されます。
  4. スレッド1がA.fun4()を呼び出すとき、他のスレッドがA.fun4()またはA.fun5()を同時に呼び出すと、fun4synchronizedはクラスであるため、後者の呼び出しはブロックされますレベル。
  5. スレッド1がA.fun4()を呼び出し、スレッド2がa1.fun1()を同時に呼び出し、ブロッキングが発生しない場合、2つのメソッドが同時に実行されます。
7
coderz
  1. decrementincrementとは異なるものをロックしているため、お互いの実行を妨げません。
  2. あるインスタンスでdecrementを呼び出すことは、別のインスタンスでdecrementを呼び出すこととは異なることをロックしますが、同じことに影響を及ぼします。

最初の意味は、incrementdecrementの呼び出しが重複すると、キャンセル(正しい)、増分、または減分になる可能性があることを意味します。

2番目は、異なるインスタンスでdecrementを2回重複して呼び出すと、二重のデクリメント(正しい)または単一のデクリメントが発生する可能性があることを意味します。

6
Jon Hanna

2つの異なるメソッド、1つはインスタンスレベル、もう1つはクラスレベルであるため、2つの異なるオブジェクトをロックしてThreadSafeにする必要があります。

4
jaleel_quest

他の回答で説明したように、静的メソッドincrement()はクラスモニターと非静的メソッドdecrement()オブジェクトモニターをロックします。

このコード例では、synchronzedキーワードを使用せずに、より良いソリューションが存在します。スレッドの安全性を実現するには、 AtomicInteger を使用する必要があります。

AtomicIntegerを使用したスレッドセーフ:

import Java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
1
Ravindra babu