7
private volatile Object obj = new MyObject();
void foo() 
{
  synchronized(obj)
  {
    obj.doWork();
  }
}
void bar()
{
  synchronized(obj)
  {
    obj.doWork();
    obj = new MyObject(); // <<<< notice this line (call it line-x)
  }
}

ある時点で、あるスレッド t_barが実行中bar()であり、別のスレッドがt_foo実行中であり、fooそれt_barが を取得したばかりで、実際には待機しているとします。objt_foo

同期ブロックが実行された後、その同期ブロックbarが実行され fooますよね? どのような値がobj表示されますか? 古いもの?それとも新しいセットbarですか?

(新しい値が見られることを願っています。それがそのようにコーディングすることの要点ですが、これが「安全な」賭けであるかどうか知りたいです)

4

5 に答える 5

2

あなたが説明した正確な状況では、はい、objfoo の同期ブロック内を読み取ると、前のバーの同期ブロックによって設定された新しい値が表示されます。

面白いのは、必ずしもその正確な状況で起こるとは限らないということです。プログラムはスレッド セーフではありません。たとえば、 の終了直後にbar()同じスレッドが別の を呼び出しbar()、 foo スレッドが古いオブジェクトをロックしている場合などです。bar スレッドは新しいオブジェクトをロックするため、2 つのスレッドが同時に実行obj.doWork()され、両方とも同じ新しいオブジェクトで実行されます。

おそらく部分的に修正できます

// suppose this line happens-before foo()/bar() calls
MyObject obj = new MyObject();

void foo()
    while(true)
        MyObject tmp1 = obj;
        synchronized(tmp1)
            MyObject tmp2 = obj;
            if(tmp2==tmp1)
                tmp2.doWork();
                return;
            // else retry

これは少なくともobj.doWork()、同じ obj に対する現在の呼び出しがないことを保証します。これobj.doWork()は、まったく同じものをロックする同期ブロックでのみ発生する可能性があるためです。obj

于 2013-04-26T17:26:19.583 に答える
1

オブジェクト参照が内部的に変更されていないかのように正常に動作します。その理由は、オブジェクトのロックのテストが 1 回だけ実行されるためです。したがって、オブジェクトが内部的に変更されても、スレッドは待機し続け、オブジェクトが同じ [変更されていない] 場合と同じように動作します。

別のことを試しました。新しいオブジェクトが作成された直後に sleep ステートメントを配置し、次のスレッドを開始すると、予想どおり両方のスレッドが同時に動作し始めました。以下のコードを参照してください。

public class ChangeLockObjectState {

    private volatile Object obj = new Object();

    void foo() {
        synchronized (obj) {
            try {
                System.out.println("inside foo");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    void bar() {
        synchronized (obj) {
            try {
                System.out.println("inside bar");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            obj = new Object(); // <<<< notice this line (call it line-x)

            System.out.println("going out of  bar");

            try {

                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println("wait over");

        }
    }

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        final ChangeLockObjectState test = new ChangeLockObjectState();

        new Thread(new Runnable() {

            @Override
            public void run() {
                test.bar();

            }
        }).start();

        Thread.sleep(6000);

        new Thread(new Runnable() {

            @Override
            public void run() {
                test.foo();

            }
        }).start();

    }

}
于 2013-04-26T16:43:58.927 に答える
0

これは安全ではなく、壊れています。ロックオンしているオブジェクトを変更しても機能しません。

スレッドが同期ブロックに入ろうとすると、必要なロックを判断するために、最初に括弧内の式を評価する必要があります。その後ロックが変更された場合、スレッドはそれを知る方法がなく、最終的に古いロックを取得して同期ブロックに入ります。その時点で、オブジェクトを見てそれを評価し、新しい参照を取得し、新しいロックを保持せずに、古い (現在は無関係な) ロックを使用してメソッドを呼び出します。同じオブジェクトに対して同時にメソッドを実行している可能性があります。

于 2013-04-26T16:43:51.763 に答える
-2

の新しい値objが読み取られます。

Happens beforeに関する標準のセクションから:

揮発性フィールド (§8.3.1.4) への書き込みは、そのフィールドの後続のすべての読み取りの前に発生します。

共有変数の定義から:

すべてのインスタンス フィールド、静的フィールド、および配列要素は、ヒープ メモリに格納されます。この章では、変数という用語を使用して、フィールドと配列要素の両方を指します。ローカル変数 (§14.4)、正式なメソッド パラメーター (§8.4.1)、および例外ハンドラー パラメーター (§14.20) は、スレッド間で共有されることはなく、メモリ モデルの影響を受けません。

同期ブロック内の読み取りは、どのオブジェクトの組み込みモニターをロックするかを決定するためobjの式の初期評価とは別のものです。objの再割り当てはobj、最初の読み取りの前に行われますが、2 回目の読み取りでは行われません。objvolatileフィールドであるため、その 2 番目の読み取りでは の更新された値が表示される必要がありますobj

于 2013-04-26T16:45:48.310 に答える