二重チェックロック:Java同時プログラミングにおけるトラップ


最近javaの同時プログラミングを勉強して、1つの前に何度も踏んだことがあって気づかなかったjavaの同時プログラミングの罠を発見します
まず、次のコードを参照してください.
public static Singleton getInstance(){
	if(instance == null){
		synchronized(Singleton.class){
			if(instance==null)
				instance = new Singleton();
		}
	}
	return instance;
}

(CSDNのコード編集機能を突っ込むのは使いにくい)
このコードはSingletonのgetInstance()メソッドであり、このメソッドを呼び出すとSingletonにインスタンスがある場合はインスタンスを返し、インスタンスがない場合はコンストラクション関数(privateのコンストラクション関数)newのインスタンスを呼び出します.このコードは、パラレル環境での単一のモードの機能を実現します.
この部分のコードはまずinstanceがnullであるかどうかを判断し、確かにnullであればsynchronizeで囲まれたコードブロックに入り、ロックをかけたことに相当して臨界領域に入ったが、nullが臨界領域に入ったと判断する過程でスレッドがnewに実例を示すことを防止するために、再ロックが完了した後、instanceがnullであるかどうかを判断し、今回やはりnullであれば、instanceがnullであることを確認でき、この場合他のスレッドがnewを1つのinstanceで試してみることもないので、安心してnewオブジェクトの作業を実行することができます.
このコードは単一スレッドの環境では間違いありませんが(くだらない話)、同時の環境では深刻な問題が発生します.
問題はjavaのコンパイラで、javaのコンパイラはバイトコードコマンドを再ソートして最適化します.5行目では、コンストラクション関数の呼び出しはinstanceが付与される前に発生するはずですが、java仮想マシン内部ではそうではありません.完全には、コンストラクション関数を呼び出したことのないinstanceオブジェクトがnewから出てきてから、それをinstanceリファレンスに値を割り当て、コンストラクション関数を呼び出してinstanceオブジェクトの要素を初期化します.
これにより、instanceが空のインスタンスオブジェクトに割り当てられたときに、別のスレッドがgetInstance()という関数を呼び出し、別のスレッドがinstanceが空ではないことを発見し、その空のinstanceオブジェクトを快適にreturnした可能性が高い.これにより、空のinstanceオブジェクトの参照が他のスレッドにストリームされ、悪事を働く.
では、どのように修正しますか?簡単です.
public synchronize static Singleton getInstance(){
	if(instance == null){
		instance = new Singleton();
	}
	return instance;
}

コマンドに直接ロックをかけると、2つのスレッドが同時に呼び出されず、同時効率が低下するように見えますが、正確性が保証されます.
------2017年11月3日、1年後に書いてあります.
実はvolatileと書いてもコマンドの並べ替えを防ぐことができます