10

Javaメモリモデルについて質問があります。問題を提示する簡単なクラスを次に示します。

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        array = new int[10];
        for (int i = 0; i < 10; i++) {
            array[i] = i;
        }
    }

    // Will always return the correct value?
    public int get(int index) {
        return array[index];
    }

}

私の知る限り、JMMは、最終フィールドの値が構築後に他のスレッドに表示されることを保証します。ただし、構築後に他のスレッドが配列に格納されている最新バージョンのデータを確実に参照できるようにしたいと思います。

もちろん、上記のコードは問題を示す単純な例です。実際には、直接バイトバッファー用の単純なキャッシュを実装したいので、一部のコレクションクラスに依存したくありません。現在、正しい動作を保証するためにReentrantReadWriteLockを使用していますが、可能であれば回避したいと思います。

4

4 に答える 4

11

この例では、すべてうまくいきます(うーん、判断を少し中断しましょう)。スレッドセーフに関しては、不変性はアンブロシアです。値を変更できない場合、同時実行性の問題の大部分はすぐには問題になりません。

Amirは、これは一般的に有用であると述べましたが、コンストラクターには、可視性を保証する変数volatileの同様のセマンティクスもあります。final詳細については、 JLSの17.5節を参照してください。基本的に、コンストラクターは、最終変数への書き込みと後続の読み取りの間に発生前の関係を形成します。

編集:コンストラクターで配列への参照を設定すると、その時点ですべてのスレッドに表示され、その後は変更されません。したがって、他のすべてのスレッドが同じ配列を参照することがわかります。しかし、配列の内容はどうですか?

現状では、配列要素にはボラティリティに関して特別なセマンティクスはありません。まるで、クラスを自分で次のように宣言したかのようです。

public class ArrayTen {
    private int _0;
    private int _1;
    // ...
    private int _9;

    public int get(int index) {
       if (index == 0) return _0;
       // etc.
    }
}

つまり、別のスレッドは、発生前の関係を確立するために何かを実行できる場合にのみ、これらの変数を参照します。そして、私の理解が正しければ、これには元のコードにわずかな変更を加える必要があります。

配列参照の設定がコンストラクターの終了前に行われることはすでにわかっています。常に当てはまる追加のポイントは、1つのスレッドでのアクションが、同じスレッドでのその後のアクションの前に発生することです。したがって、最初に配列フィールドを設定し、次に最後のフィールドを割り当てることでこれらを組み合わせて、この推移的な可視性の保証を得ることができます。もちろん、これには一時変数が必要です。

public class ImmutableIntArray {

    private final int[] array;

    public ImmutableIntArray() {
        int[] tmp = new int[10];
        for (int i = 0; i < 10; i++) {
            tmp[i] = i;
        }
        array = tmp;
    }

    // get() etc.
}

一見無関係に見える割り当てと人口の順序を切り替えたので、これは安全であることが保証されていると思います。

しかし、繰り返しになりますが、私が見逃したことがあるかもしれません。これは、並行性の保証が期待したほど堅牢ではないことを意味します。この質問は、非常に単純なことをしていると思っていても、防弾マルチスレッドコードを書くのが難しい理由と、正しく行うために多くの考えと注意(そしてバグ修正)が必要な理由の優れた例です。

于 2011-07-08T14:46:28.657 に答える
3

あなたの例は完全に正しくありません。最終的なフィールド保証を取得するには、次のものが必要です。

public ImmutableIntArray() {
    int tmparray = new int[10];
    for (int i = 0; i < 10; i++) {
        tmparray[i] = i;
    }
    array = tmparray;
}
于 2011-07-08T16:06:41.880 に答える
2

オブジェクトへの最終参照と同じセマンティクスが配列で提供されていると思います。スペックは述べています

オブジェクトが完全に初期化された後でのみオブジェクトへの参照を表示できるスレッドは、そのオブジェクトの最終フィールドの正しく初期化された値を表示することが保証されます。

それはまた言います

また、少なくとも最終フィールドと同じくらい最新の、最終フィールドによって参照されるオブジェクトまたは配列のバージョンも表示されます。

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5

于 2011-07-08T16:17:10.787 に答える
0

配列の変更はImmutableIntArrayで確認できると思います。JLSに関する私の読書から、コンストラクターが終了するときに[フリーズ]アクションが実行されるはずです。私が思う一時配列の使用は役に立たないと思います:

int tmparray = new int[10];
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
array = tmparray;

最終的なフィールド保証を取得するには、コンストラクターが終了する前に[フリーズ]が必要になります。

int tmparray = new int[10];
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
array = tmparray;
[freeze]

とにかく、[フリーズ]はゲートを開いたままにして、その上の命令を並べ替えるために、同じことを行います。

int tmparray = new int[10];
array = tmparray; 
for (int i = 0; i < 10; i++) {
    tmparray[i] = i;
}
[freeze]

[freeze]は、少なくとも[StoreStore]を含むように実装されています。この[StoreStore]バリアは、構築されたインスタンスが公開される前に発行する必要があります。

JSR-133クックブックから:

オブジェクトを他のスレッドから見えるようにする可能性のある、コンストラクター内のファイナルのストアをコンストラクター外のストアの下に移動することはできません。(以下に示すように、これにはバリアの発行も必要になる場合があります)。同様に、次の3番目の割り当てで最初の2つを並べ替えることはできません。v.afield= 1; x.finalField = v; ...; sharedRef = x;

そして、これは(JSR-133クックブック)によって行われていると思います :

すべてのストアの後、最終フィールドを持つクラスのコンストラクターから戻る前に、StoreStoreバリアを発行します。

したがって、他のすべてのコンストラクタストアが完了する前に、sharedRefに格納することはできません。

(JSR133仕様)の「最終フィールドからの推移的保証」で検索できます。

于 2014-12-12T14:24:12.867 に答える