これを仮定しましょう:
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;
}
そのようなことが起こり得ますか?
ElementDataでも同様の状況が起こりますか?
TL; DR:同期によって操作の原子性と可視性が保証されるため、正しい同期では、記述した問題は不可能です。
JVMの実行方法Javaコードはかなり複雑です。Javaコード内の式とステートメントに対応する命令を自由に並べ替えて、スレッドが操作の順序を変更したことがわからない場合は、より効率的に実行します。
本質的には、「どうやって仕事を終わらせるかは気にせず、[時間]までに[仕事]を終わらせればよい」と言う上司のようなものです。
これの難しさは、スレッド内での再順序付けを見ることができないようにする必要があることを示していますが、異なるスレッドが互いに異なる順序で動作しているのを見ることができないということではありません。
これは頭が回転するものです。単純化の概念はhappens-beforeの考え方です。 2つのスレッドで実行できる特定の処理があり、1つのスレッドによって実行された処理が、他のスレッドがそれらの結果を使用しようとするときにすでに発生しているように見えます。文字通り、一方のスレッドの事物は、もう一方の事柄の「前に起こった」。 (作業の類推を続けると、これは、完了した作業を同僚に引き渡さなければならないようなものです。彼らは、あなたが完了したものを取り、作業に関係なく、その作業を行うことができます方法あなたはそれを完了しました)。
発生前の関係を作成するよく知られていることがいくつかあります。この質問に関連するものは次のとおりです。
したがって、揮発性と同期は、1つのスレッドによって実行された[何か]が他のスレッドによって認識されることを保証するために必要な「発生前」を作成する両方の方法です。
しかし、2つの間に違いがあります。
ArrayList
に追加する場合は、アトミック性が必要です。これは、サイズを増やすand新しい配列要素を割り当てるという複数のことを行うためです。
変数を揮発性にすることも、正確さに関しては意味がありませんが、モーダルの場合、コードが遅くなります。この場合、ArrayList
は単一のスレッドからのみアクセスされます。
したがって、コードが正しく同期されている場合、つまり、リストへのすべてのアクセスは同じもの、たとえばリスト自体で同期されます-原子性と同期の可視性プロパティ。