シングルトン
暗記を学ぶのは無意味だといつも感じていましたが、自然な視点からデザインパターンのひとつであるシングルトンパターンを学びましょう。
いわゆるシングルトンモードは、その名前が示すように、グローバルに1つのインスタンスしか持つことができません。通常、コンストラクターnewを介してインスタンスを作成することは誰もが知っているので、シングルトンがプライベートであることを確認する場合は、コンストラクターをプライベートにし、変数をプライベートおよび静的にする必要があります(変更およびアクセスを防ぐため)。
Ⅰ怠惰なマンスレッドは安全ではありません
次の実装では、クラスがロードされたときにプライベート静的変数uniqueInstanceがインスタンス化されません。これの利点は、クラスが使用されていない場合、uniqueInstanceがインスタンス化されないため、リソースが節約されることです。(これは「怠惰な」中国風です)
マルチスレッド環境でのこの実装は安全ではありません。複数のスレッドが同時に入る可能性がif (uniqueInstance == null)
あり、今回はuniqueInstanceがnullの場合、複数のスレッドuniqueInstance = new Singleton();
がステートメントを実行するため、複数のuniqueInstanceがインスタンス化されます。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
Ⅱ空腹のチャイニーズスタイル-スレッドセーフ
怠惰なスレッドのセキュリティの問題は、主にuniqueInstanceが複数回インスタンス化されることが原因です。uniqueInstanceを直接インスタンス化する方法では、スレッドのセキュリティの問題は発生しません。オブジェクトはクラスのロード時に作成され、あなたが私に来て直接戻ってきます。香り高い!シンプル!
ただし、直接インスタンス化方法では、インスタンス化の遅延によってもたらされるリソース節約のメリットも失われます。
private static Singleton uniqueInstance = new Singleton();
Ⅲ怠惰なマンスレッドセーフ
getUniqueInstance()メソッドをロックするだけで、一度に1つのスレッドのみがメソッドに入ることができるため、複数のuniqueInstanceのインスタンス化を回避できます。
ただし、スレッドがメソッドに入ると、uniqueInstanceがインスタンス化されている場合でも、メソッドに入ろうとする他のスレッドは待機する必要があります。これにより、スレッドが長時間ブロックされるため、この方法にはパフォーマンスの問題があるため、お勧めしません。インスタンス化されていても考えてみますが、メソッドに入るたびに同期してブロックされてしまいます。誰が我慢できますか?
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
Ⅳロックスレッドの安全性を再確認してください
uniqueInstanceをインスタンス化する必要があるのは一度だけで、その後は直接使用できます。ロック操作は、インスタンス化されたコードの部分でのみ実行する必要があり、uniqueInstanceがインスタンス化されていない場合にのみ、ロックする必要があります。
ダブルチェックロックは、最初にuniqueInstanceがインスタンス化されているかどうかを判別し、インスタンス化されていない場合は、インスタンス化ステートメントがロックされます。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
次の実装について考えてみます。これは、ifステートメントを1つだけ使用します。uniqueInstance == nullの場合、両方のスレッドがifステートメントを実行すると、両方のスレッドがifステートメントブロックに入ります。ifブロックにはロック操作がありますが、2つのスレッドがuniqueInstance = new Singleton();
このステートメントを実行しますが、問題がありますが、2回インスタンス化されます。したがって、ダブルチェックロックを使用する必要があります。つまり、2つのifステートメントが必要です。最初のifステートメントはuniqueInstanceがインスタンス化された後のロック操作を回避するために使用され、2番目のifステートメントはロックされているため、次の場合にのみ使用できます。 1つのスレッドが入ると、uniqueInstance == nullの場合、2つのスレッドによる同時インスタンス化操作はありません。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
また、uniqueInstanceをvolatileキーワードで変更する必要があります。uniqueInstance = new Singleton();
このコードは、実際には次の3つのステップで実行されます。
- uniqueInstanceにメモリスペースを割り当てます
- uniqueInstanceを初期化します
- 割り当てられたメモリアドレスをuniqueInstanceにポイントします
ただし、JVMの命令再配置の性質上、実行順序は1> 3> 2になる場合があります。シングルスレッド環境では命令の再配置に問題はありませんが、マルチスレッド環境では、スレッドはまだ初期化されていないインスタンスを取得します。たとえば、スレッドT1は1と3を実行し、T2はgetUniqueInstance()を呼び出した後にuniqueInstanceが空ではないことを検出したため、uniqueInstanceを返しますが、uniqueInstanceはまだ初期化されていません。
volatileを使用すると、JVM命令の並べ替えが禁止され、マルチスレッド環境での通常の動作が保証されます。
Ⅴ静的内部クラスの実現
Singletonクラスがロードされると、静的内部クラスSingletonHolderはメモリにロードされません。この時点でSingletonHolderがロードされるときgetUniqueInstance()
にトリガーするメソッドを呼び出した場合のみ、SingletonHolder.INSTANCE
INSTANCEインスタンスを初期化し、INSTANCE JVMが1回だけインスタンス化されるようにします**(複数のスレッドが同時にSingletonHolder()を呼び出すかどうかを尋ねる学生もいますが、そうではありません)安全ですか?これには、クラスを同時に初期化する複数のスレッドの場合、1つのスレッドのみがこのクラスの()メソッドを実行し、他のスレッドはブロックして、アクティブなスレッドが()メソッドを実行するまで待機する必要があります) **。
このメソッドには、レイジー初期化の利点があるだけでなく、JVMによって提供されるスレッドセーフもサポートします。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
Ⅵ列挙の実現
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println("reflection:"+enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
firstName
secondName
secondName
reflection:secondName
この実装により、リフレクション攻撃を防ぐことができます。他の実装では、setAccessible()メソッドを使用してプライベートコンストラクターのアクセスレベルをパブリックに設定し、コンストラクターを呼び出してオブジェクトをインスタンス化できます。この種の攻撃を防ぎたい場合は、追加する必要があります。複数のインスタンス化を防ぐためのコンストラクターコード。この実装は、JVMによって1回だけインスタンス化されることが保証されているため、上記のリフレクション攻撃は発生しません。
この実装は、複数のシリアル化とシリアル化の後に複数のインスタンスを取得しません。他の実装では、transientを使用してすべてのフィールドを変更し、シリアル化および逆シリアル化の方法を実装する必要があります。
参考リンク: デザインPattern-Singleton.md。
https://github.com/CyC2018/CS-Notes/blob/master/notes/Design Pattern-Singleton.md#ⅳ-ダブルロック・スレッドの安全性を確認してください