Javaメモリモデルhappens-before法則


JMM仕様:
The rules for happens-before are:
Program order rule. Each action in a thread happens-before every action in that thread that comes later in the program order.
Monitor lock rule. An unlock on a monitor lock happens-before every subsequent lock on that same monitor lock.
Volatile variable rule. A write to a volatile field happens-before every subsequent read of that same field.
Thread start rule. A call to Thread.start on a thread happens-before every action in the started thread.
Thread termination rule. Any action in a thread happens-before any other thread detects that thread has terminated, either by successfully return from Thread.join or by Thread.isAlive returning false.
Interruption rule. A thread calling interrupt on another thread happens-before the interrupted thread detects the interrupt (either by having InterruptedException tHRown, or invoking isInterrupted or interrupted).
Finalizer rule. The end of a constructor for an object happens-before the start of the finalizer for that object.
Transitivity. If A happens-before B, and B happens-before C, then A happens-before C.
----------------------------
happens-beforeとは?
happens-beforeは「何が必ず何かの前に実行される」、つまり順序性を保証します.
CPUは我々がコードを書く順番でメモリのアクセスプロセスを実行しないことができるので、つまり命令が乱れたり並列に実行されたりするので、
上記のhappens-beforeで規定されている場合のみ、順序性が保証されます.
次のようになります.

public class Test {

	private int a = 0;

	private long b = 0;

	public void set() {
		a = 1;
		b = -1;
	}

	public void check() {
		if (! ((b == 0) || (b == -1 && a == 1))
			throw new Exception("check Error!");
	}
}

set()メソッドの実行:
1.コンパイラは文の実行順序を再配置することができ、bはaの前に値を割り当てることができる.メソッドが埋め込まれている場合、コンパイラは他の文を並べ替えることもできます.
2.プロセッサは、これらの文を同時に実行するまで、これらの文のマシン命令の実行順序を変更することができる.
3.キャッシュ制御部によって制御される記憶システムは、他の計算および記憶動作と同時に発生する可能性のある、対応する記憶部の書き込み動作順序を再配置することもできる.
4.コンパイラ、プロセッサ、ストレージシステムは、この2つの文のマシン命令を交差させることができます.
例えば、32ビットのマシンでは、まずbの高位を書いてからaを書いて、最後にbの低位を書いてもいいです.
5.コンパイラ、プロセッサ、ストレージシステムは、変数に対応するメモリセルを常に元の値に保つことができる.
次のcheck呼び出しが更新されるまで、コードが正常に動作することを保証するために、対応する値(例えば、CPUのレジスタ)を何らかの方法で維持する.
...
単一スレッド(または同期)の場合、上記のcheck()は決してエラーを報告しません.
しかし、非同期マルチスレッド実行時は可能です.
また、複数のCPU間のキャッシュもリアルタイム同期を保証する、
つまり、変数に値を割り当てたばかりで、別のスレッドはすぐに値を取得し、古い値(またはnull)を取得する可能性があります.
2つのスレッドは異なるCPUで実行されるため、キャッシュ値が異なるため、
synchronizedまたはvolatileまたはfinalの状況でのみ正確性が保証されます.
synchronizedではlockの機能しか覚えておらず、スレッド間の可視性の問題を忘れている人が多い.
次のようになります.

public class Test {

    private int n;

    public void set(int n) {
        this.n = n;
    }

    public void check() {
        if (n != n)
            throw new Exception("check Error!");
    }
}

check()のn!=nは、同じ値を指すが、非同期の場合に発生する可能性が高いため、永遠に成立しないようだ.
また、JMMは作成プロセスの原子性を保証せず、読み書きが同時に行われると、不完全なオブジェクトが見える可能性があります.
これもなぜ単例モードで有名な「二重検査例」の方法がJavaでは通じないのか.(ただし.Netのメモリモデルはこれを保証します)
もちろん、Javaでは、単一の例の遅延ロードは、別のスキームで実装できます(スキーム4):
シナリオ1:非遅延ロード単一クラス

public class Singleton {

  private Singleton(){}

  private static final Singleton instance = new Singleton();

  public static Singleton getInstance() {
    return instance;   
  } 
}

シナリオ2:単純な同期遅延ロード

public class Singleton { 

  private static Singleton instance = null;

  public static synchronized Singleton getInstance() {
    if (instance == null)
      instance = new Singleton();
    return instance;   
  } 

} 

シナリオ3:二重検査例遅延ロード
過剰な同期を避けることを目的とし、
ただし、同期ブロックの外にif(instance==null)が存在するが、不完全なインスタンスが表示される可能性があるため、Javaでは行が通じません.
JDK5.0以降のバージョンinstanceがvolatileであれば可能

public class Singleton { 

  private static Singleton instance = null;

  public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;   
  } 

} 

シナリオ4:クラス・ローダの遅延ロード

public class Singleton { 

  private static class Holder {
  	static final Singleton instance = new Singleton();
  }

  public static Singleton getInstance() {
    return Holder.instance;   
  } 

}