web-dev-qa-db-ja.com

フィールドが揮発性でない場合、外部同期されたArrayListスレッドは安全ですか?

これを仮定しましょう:

  1. 単一のArrayListがあります
  2. リストは複数のスレッドによってアクセスされます。スレッドは要素を追加し、すべての要素を反復できます。
  3. すべてのアクセスは外部で同期されます。したがって、2つのスレッドが同時にリストにアクセスすることはできません。

ArrayListソースコードを見ると、sizeフィールドとelementDataフィールドは揮発性ではないことがわかります。

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

また、addメソッドを見てみましょう。

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

そのようなことが起こり得ますか?

  1. リストに4つの要素があると仮定しましょう。
  2. スレッドAが新しい要素を追加します。サイズが5に更新されます。
  3. スレッドBは新しい要素を追加します。サイズはキャッシュされ、スレッドBは古い値(4)を参照します。そのため、新しい要素を追加する代わりに、最後の要素が上書きされます。

ElementDataでも同様の状況が起こりますか?

4
michalg

TL; DR:同期によって操作の原子性と可視性が保証されるため、正しい同期では、記述した問題は不可能です。


JVMの実行方法Javaコードはかなり複雑です。Javaコード内の式とステートメントに対応する命令を自由に並べ替えて、スレッドが操作の順序を変更したことがわからない場合は、より効率的に実行します。

本質的には、「どうやって仕事を終わらせるかは気にせず、[時間]までに[仕事]を終わらせればよい」と言う上司のようなものです。

これの難しさは、スレッド内での再順序付けを見ることができないようにする必要があることを示していますが、異なるスレッドが互いに異なる順序で動作しているのを見ることができないということではありません。

これは頭​​が回転するものです。単純化の概念はhappens-beforeの考え方です。 2つのスレッドで実行できる特定の処理があり、1つのスレッドによって実行された処理が、他のスレッドがそれらの結果を使用しようとするときにすでに発生しているように見えます。文字通り、一方のスレッドの事物は、もう一方の事柄の「前に起こった」。 (作業の類推を続けると、これは、完了した作業を同僚に引き渡さなければならないようなものです。彼らは、あなたが完了したものを取り、作業に関係なく、その作業を行うことができます方法あなたはそれを完了しました)。

発生前の関係を作成するよく知られていることがいくつかあります。この質問に関連するものは次のとおりです。

  • 揮発性変数の書き込み前に発生同じ変数の読み取り。これは、「データはスレッドによってキャッシュされるのではなく、常にメインメモリに書き込まれ、メインメモリから読み取られる」という用語でしばしば説明されます。
  • 同期ブロックの終了前に発生同じモニターで同期ブロックに入る。つまり、1つのスレッドによって同期されたブロック内で発生する書き込みは、同じもので同期されたコードを実行する他のスレッドから見える

したがって、揮発性と同期は、1つのスレッドによって実行された[何か]が他のスレッドによって認識されることを保証するために必要な「発生前」を作成する両方の方法です。

しかし、2つの間に違いがあります。

  • 揮発性はあなたに可視性を与えます:それは書き込みが目に見えることを保証します。
  • 同期は可視性を提供しますand原子性:書き込みが可視であることを保証しますが、特定のモニターが保持されている限り、他の誰かが同時に何かを実行していないことを保証します。

ArrayListに追加する場合は、アトミック性が必要です。これは、サイズを増やすand新しい配列要素を割り当てるという複数のことを行うためです。

変数を揮発性にすることも、正確さに関しては意味がありませんが、モーダルの場合、コードが遅くなります。この場合、ArrayListは単一のスレッドからのみアクセスされます。

したがって、コードが正しく同期されている場合、つまり、リストへのすべてのアクセスは同じもの、たとえばリスト自体で同期されます-原子性と同期の可視性プロパティ。

6
Andy Turner