5

コード スニペット - 1

class RequestObject implements Runnable
{
    private static Integer nRequests = 0;

    @Override
    public void run()
    {       
        synchronized (nRequests)
        {
            nRequests++;
        }
    }
}

コード スニペット - 2

public class Racer implements Runnable
{
    public static Boolean won = false;    

    @Override
    public void run()
    {
        synchronized (won)
        {
            if (!won)
            won = true;
        }
    }        
}

最初のコード スニペットで競合状態が発生しました。これは、(Integer 型の) 不変オブジェクトのロックを取得していたためだと理解しました。

私は 2 番目のコード スニペットを作成しましたが、これもまた、'Boolean' が不変であることに影響されません。しかし、これは機能します (出力実行に競合状態は表示されません)。以前の質問の解決策を適切に理解した場合、以下は、問題が発生する可能性のある 1 つの方法です。

  1. スレッド 1 が指すオブジェクト (A など) のロックを取得します。won
  2. スレッド 2 は、 が指すオブジェクトのロックを取得しようとしwon、A の待機キューに入ります。
  3. won = trueスレッド 1 は同期ブロックに入り、A が false であることを確認し、 (A はレースに勝ったと考えている) と言って新しいオブジェクト (B など) を作成します。
  4. 'won' は B を指すようになりました。スレッド 1 はオブジェクト A のロックを解放します (もはや によって指されていませんwon) 。
  5. ここで、オブジェクト A の待機キューにあったスレッド 2 が起動され、オブジェクト A のロックが取得されますが、オブジェクト A はまだロックされていますfalse(不変にそうです)。今度は同期ブロックに入り、自分も勝ったと想定しますが、これは正しくありません。

2 番目のコード スニペットが常に正常に動作するのはなぜですか??

4

4 に答える 4

8
    synchronized (won)
    {
        if (!won)
        won = true;
    }

runここでは、メソッドの最初の実行後に消えるため気付かない一時的な競合状態があります。その後、変数は常に表現wonの同じインスタンスを指すため、ミューテックス ロックとして適切に機能します。Booleantrue

これは、実際のプロジェクトでそのようなコードを書くべきだと言っているわけではありません。すべてのロック オブジェクトを変数に割り当てて、final変数が変更されないようにする必要があります。

于 2013-08-04T09:38:03.340 に答える
2

オブジェクトが不変であるかどうかは、synchronizedステートメントのロック オブジェクトとして適しているかどうかとは関係ありません。ただし、重要な領域の同じセットに入るすべてのスレッドで同じオブジェクトを使用すること重要です (したがって、オブジェクト参照を作成するのが賢明かもしれません) が、オブジェクト自体は、その「ロック性」に影響を与えることなく変更できます。さらに、異なる参照変数がすべて同じオブジェクトを参照している限り、2 つ (またはそれ以上) の異なるステートメントで異なる参照変数を使用しても、相互に排他的である可能性があります。finalsynchronized

上記の例では、クリティカル領域のコードが 1 つのオブジェクトを別のオブジェクトに置き換えますが、これが問題です。ロックは参照ではなくオブジェクトにあるため、オブジェクトを変更することはできません。

于 2013-08-05T03:46:21.383 に答える
2

最初のコード スニペットで競合状態が発生しました。これは、(Integer 型の) 不変オブジェクトのロックを取得していたためだと理解しました。

実際、それはまったく理由ではありません。不変オブジェクトのロックを取得すると、問題なく「機能」します。問題は、おそらく何も役に立たないということです...

最初の例が壊れる本当の理由は、間違ったものをロックしているからです。これを実行すると - nRequests++- 実際に行っていることは、次の非アトミック シーケンスと同等です。

    int temp = nRequests.integerValue();
    temp = temp + 1;
    nRequests = Integer.valueOf(temp);

つまり、別のオブジェクト参照をstatic変数に割り当てていますnRequests

問題は、スニペットでは、変数が更新されるたびに、スレッドが別のオブジェクトで同期することです。これは、各スレッドがロックされるオブジェクトへの参照を変更するためです。

適切に同期するには、すべてのスレッドが同じオブジェクトをロックする必要があります。例えば

class RequestObject implements Runnable
{
    private static Integer nRequests = 0;
    private static final Object lock = new Object();

    @Override
    public void run()
    {       
        synchronized (lock)
        {
            nRequests++;
        }
    }
}

実際、2 番目の例には最初の例と同じ問題があります。テストで気付かない理由は、 からwon == falseへの遷移won == trueが 1 回だけ発生するためです。そのため、潜在的な競合状態が実際に発生する可能性ははるかに低くなります。

于 2013-08-05T03:22:52.193 に答える
0

実際、2 番目のコードもスレッド セーフではありません。以下のコードを使用してご自身で確認してください (最初の print ステートメントが 2 になることがあることがわかります。これは、同期ブロック内に 2 つのスレッドがあることを意味します!)。結論: Code snippet - 1 & Code snippet - 2 は基本的に同じであり、したがってスレッドセーフではありません...

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class Racer implements Runnable {
    public static AtomicInteger counter = new AtomicInteger(0);
    public static Boolean won = false;    

    @Override
    public void run() {
        synchronized (won) {
            System.out.println(counter.incrementAndGet()); //should be always 1; otherwise race condition
            if (!won) {
                won = true;
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(counter.decrementAndGet()); //should be always 0; otherwise race condition
        }
    }   

    public static void main(String[] args) {
        int numberOfThreads = 20;
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for(int i = 0; i < numberOfThreads; i++) {
            executor.execute(new Racer());
        }

        executor.shutdown();
    }
}
于 2013-08-04T10:01:29.917 に答える