2

次のコードはスレッドセーフと見なされますか、つまり、リストへの書き込みはリストへの読み取りよりも前に発生することが保証されていますか? これが Java メモリ モデルで安全と見なされるかどうかを理解しようとしてきましたが、不明です。

synchronized基本的なフロー分析を通じて、考えられるすべてのスレッドが以下のループに到達する前に初期化ブロックを通過する必要があることが保証されているように見えますがfor、そのリストの反復は決定論的でスレッドセーフでしょうか? 以下のリストを使用する前に初期化が行われることが保証されているかどうかはわかりません。

これがクラス内の唯一のメソッドであると仮定します。同期ブロック内で反復を移動するとスレッドセーフが保証されることはわかっていますが、この構造が安全かどうかを知りたいと思っています。

また、リストがクラスを決してエスケープしないと仮定します。

Java メモリ モデルは、こちらの JLS で説明されています: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4

private List<Foo> list;
private final Object monitor = new Object();

public void bar() {
    synchronized (monitor) {
        if (list == null) {
            list = new ArrayList<>();
            list.add(...); // expensive operation
            list.add(...); // expensive operation
            list.add(...); // expensive operation
        }
    }

    for (Foo foo : list) {
        // do something with foo
    }
}
4

4 に答える 4

7

これがリストを構造的に変更する唯一の場所である場合に限り、スレッドセーフです。

リストを別の場所で (たとえば、 を使用してclear())変更すると、他の場所で が使用synchronizedされている場合でも、反復処理中にリストを簡単に変更できます。

リストを他の場所で変更するつもりがない場合は、 を使用Collections.unmodifiableList()してこの事実を確認 (および文書化) することをお勧めします。

于 2013-02-13T15:40:37.297 に答える
2

JLS #17.4.5は、次のことを保証します。

モニターのロック解除は、そのモニターの後続のすべてのロックの前に発生します。

また、同期ブロックが同時に実行されず、for ループで並べ替えられないことも保証されます。

したがって、到着してモニターを取得する最初のスレッド (T0 と呼びましょう) がリストを初期化します。T0 が同期ブロックを終了すると、スレッド内セマンティクスにより、for ループが T0 で期待どおりに実行されることが保証されます。

その後に到着するすべてのスレッドは、モニターが使用可能になるのを待ち、それを取得します。上記の保証により、T0 によって初期化された (つまり、null ではなく、入力された) リストが表示されます。また、スレッド内セマンティクスにより、for ループは期待どおりに実行されます。

結論: リストが他の場所に書き込まれず、すべての読み取りがモニターの取得後に行われる場合、コードは安全です。

于 2013-02-13T17:13:24.340 に答える
1

その同期されたメソッド内から離れてリストを再度変更しない場合、公開したコードは安全です。他のメソッド (bar() ではない) が同じリストを使用する場合、コードは安全ではありません。また、宣言する必要がありますfinal List list

于 2013-02-13T15:39:57.020 に答える
1

synchronized(list)、そこに含まれるコードのブロックにのみ適用されます。for each ループを使用してリストを反復処理しているときに別のスレッドがリストを変更すると、問題が発生します。

于 2013-02-13T15:41:04.680 に答える