通常のシングルトンパターン
ハングリーチャイニーズスタイル:staticキーワードを使用して、クラスの初期化時に静的メソッドが呼び出されます
public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return singleton; } }
短所:このオブジェクトは現時点では使用できず、リソースを浪費する可能性があります(参照:クラスの初期化タイミング)
シングルトンモードの最適化
レイジー:nullとして宣言され、使用時に初期化されますが、スレッドの同時実行を防ぐためにロックする必要があります。2つのオブジェクトが生成されます
パブリック クラスシングルトン{ プライベート 静的 シングルトンシングルトン= null ; private Singleton(){ } public synchronized static Singleton getInstance(){ if(singleton == null ){ System.out.println( "我的其他业务" ); singleton = new Singleton(); } シングルトンを返す。 } }
短所:ロックする必要のないメソッド内の一部のビジネスコードもロックされ、ロックの粒度が粗い
徹底的な最適化(ロックの調整)
ロックを調整するときに2つのロックを追加する必要がある
パブリック クラスシングルトン{ プライベート 静的 シングルトンシングルトン= null ; private Singleton(){ } public static Singleton getInstance(){ if(singleton == null ){ System.out.println( "我的其他业务" ); 同期(シングルトンクラス){ // ダブルロックを確認し た場合(シングルトン== nullの){ シングルトン = 新しいシングルトン(); } } } シングルトンを返す。 } }
DDLが2つのロックを追加する理由:複数のスレッドが同時にビジネスロジックを実行しないようにする
シングルトンの最終バージョン:CPU命令の再配置によるセキュリティ
CPUが再配置を命令する可能性があるため、変数を宣言するときは、volatileキーワードを追加する必要があります
private static volatile Singleton singleton = null ;
以下は、volatileキーワードが追加された理由を説明しています
私たちのアイデアidea-view-Show Bytecodeでは、メソッドのバイトコードを確認できます。
たとえば
オブジェクトo = new Object();
バイトコード
新しいjava / lang / Object DUP INVOKESPECIAL java / lang / Object。<init> ()V ASTORE 1
新しいオブジェクトを作成する場合、基本的に3つのステップがあります
1)ヒープメモリ内のオブジェクトを適用してメモリの一部を割り当てます。このとき、オブジェクト内の値はデフォルト値です。
2)次に、コンストラクターを呼び出して初期化します。
3)ヒープメモリの参照をo oに割り当てて、ヒープメモリ内のオブジェクトオブジェクトを実行し、関連付けを確立します。
メモリの初期化と確立の関連付けを割り当てる
線形1newの場合、メモリ割り当ての最初のステップに進むと、CPU命令の並べ替えが発生し、最初に関連付けを確立します(この時点では、関連付けられたオブジェクト値は初期化されていないため空です)。
これは、スレッド2が入り、オブジェクトが空でないことを検出したときです(3番目のステップで関連付けが確立されているためです)。直接使用され、このときに使用されるオブジェクトは半製品です(スレッド1が初期化されていないため)
volatileの役割には2つのポイントがあります(これらの2つのポイントの実装原則は来週更新されます...)
1)マルチスレッドの直接的な可視性(キャッシュ整合性プロトコルは、キャッシュラインのデータ整合性を維持します)
2)CPU命令の順序変更を防止します(JVM仕様ではメモリへのバリアが必要です)
CPUがリオーダーする理由
CPUが実行する2つの命令があり、2つの命令間に直接の依存関係がない場合、
最初の命令の実行中にメモリからデータを読み取る必要がある場合、CPUの動作速度はメモリの読み取り速度の100倍であるため、2番目の命令を最初に実行できます。
そうすることで、計算機の全体的な操作効率を上げることができます
たとえば、お湯を沸かしているときに食器を洗うことができますが、最初に水を沸騰させますが、最初に食器を洗う動作が実行される場合があります。
このとき、2番目の命令が最初の命令より先に実行される場合があります。最初の実行では、1 2の後ろのCPU実行順序は2 1になる場合があります(2つの命令であるため、この可能性は同時実行の場合にのみ発生します)。
public static void main(String [] args)はException { int i = 0 ;をスローします 。 for (;;){ i ++ ; x = 0; y = 0 ; = 0; b = 0 ; スレッド1 = new Thread(()-> { a = 1; x = b; }); スレッド2 = new Thread(()-> { b = 1; y = a; }); one.start(); two.start(); one.join(); two.join(); String result = "第" + i + "次执行(" + x + "" + y + ")" ; if(x == 0 && y == 0 ){ System.out.println(result); 休憩; } その他{ } } }
上記のコードが214609回実行された後、x = 0 y = 0と出力され、CPUに命令の順序変更があることがわかります