JUC - マルチスレッドのシングルトンモード (8)

シングルトン パターン (Singleton Pattern) は非常にシンプルなデザイン パターンの 1 つで、使用するオブジェクトをグローバルに一意にする場合、このパターンを使用してオブジェクトの一意性を保証する必要があります。さらに、オブジェクトのインスタンス化の繰り返しを回避し、メモリ オーバーヘッドを削減できます。

シングルトン クラスは、このクラスのオブジェクトをインスタンス化せずに直接アクセスできる唯一のオブジェクトにアクセスする方法を提供します。

シングルトンには主に以下の作成方法があります

1. ハングリー チャイニーズ スタイル - 静的変数 (スレッド セーフ)

/**
 * 1、饿汉式-静态变量(线程安全)
 */
public class Hungry1 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry1(){
    }

    // 2、内部创建对象
    private static Hungry1 singleton = new Hungry1();

    // 3、对外获取实例的方法
    public static Hungry1 getInstance(){
        return singleton;
    }
}

利点: 簡単な記述; スレッド同期の問題を回避

短所: クラスがロードされるとインスタンス化が完了し、遅延ロードの効果が得られません。このインスタンスを最初から最後まで使用しないと、メモリの浪費が発生します 

2. ハングリー チャイニーズ スタイル - 静的コード ブロック (スレッド セーフ)

/**
 * 2、饿汉式-静态代码块(线程安全)
 */
public class Hungry2 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry2(){
    }

    // 2、内部静态代码块中创建对象
    private static Hungry2 singleton;
    static{
        singleton = new Hungry2();
    }

    // 3、对外获取实例的方法
    public static Hungry2 getInstance(){
        return singleton;
    }
}

メリット・デメリットは上記の通り 

3. 怠惰なスタイル - 空でないことの単純な判断 (マルチスレッドの同時実行は安全ではなく、シングルスレッドは効果がありません)

/**
 * 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)
 */
public class Lazy1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy1() {}

    // 2、声明类成员变量singleton
    private static Lazy1 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy1 getInstance(){
        if (singleton == null){
            singleton = new Lazy1();
        }
        return singleton;
    }
}

利点: オブジェクトは使用時にのみ生成されるため、メモリ オーバーヘッドを削減できます。

短所: スレッドは安全ではなく、シングル スレッドにのみ適用されます。複数のスレッドがアクセスする場合、複数のオブジェクトを生成できます。シングルトン モードの要件を満たしていません。

マルチスレッドでは、1 つのスレッドが if (singleton == null) 判定文ブロックに入り、それを実行する前に別のスレッドも判定文を渡し、この時点で複数のインスタンスが生成されます。

4. レイジー スタイル -- 同期ロック方式 (スレッド セーフ、低効率)

/**
 * 4、懒汉式-synchronized锁方法(线程安全,效率低)
 */
public class Lazy2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy2() {}

    // 2、声明类成员变量singleton
    private static Lazy2 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy2 getInstance(){
        if (singleton == null){
            singleton = new Lazy2();
        }
        return singleton;
    }
}

長所: スレッドセーフ

短所: 効率が低すぎる同期ロックはメソッド全体をロックする、次のスレッドは前のスレッドがロックを解放するのを待たなければならない、効率が非常に低い、使用は推奨されない

5. レイジー スタイル -- 同期化された同期コード ブロック (マルチスレッドの同時実行は安全ではありません)

/**
 * 5、懒汉式-synchronized同步代码块(多线程并发不安全)
 */
public class Lazy3 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy3() {}

    // 2、声明类成员变量singleton
    private static Lazy3 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy3 getInstance(){
        if (singleton == null){
            synchronized (Lazy3.class){
                singleton = new Lazy3();
            }
        }
        return singleton;
    }
}

利点: オブジェクトは使用時にのみ生成されるため、メモリ オーバーヘッドを削減できます。

短所: スレッドが安全でない

スレッドが if (singleton == null) 判定文ブロックに入ると、実行する前に別のスレッドも判定文を渡し、複数のインスタンスが生成されます

6、怠惰なスタイル - ダブル チェック DCL (スレッド セーフ; 一般的に使用)

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 */
public class Lazy4 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4() {}

    // 2、声明类成员变量singleton
    private volatile static Lazy4 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4 getInstance(){
        if (singleton == null){
            synchronized (Lazy4.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰
                     */
                    singleton = new Lazy4();
                }
            }
        }
        return singleton;
    }
}

Double-Checkの概念は、マルチスレッドの開発者にとってなじみのないものではありません. コードに示されているように、2 つの if (singleton == null) チェックを実行することで、スレッドの安全性を保証できます。このように、インスタンス化コードは 1 回実行するだけでよく、後で再度アクセスしたときに if (singleton == null) を判定し、インスタンス化オブジェクトを直接返します。 

利点: 高効率、スレッドセーフ

欠点: シングルトン パターンはリフレクションによって破壊される可能性があります 

リフレクションは二重の怠惰なスタイルのシングルトンを破棄します

1. リフレクションは二重遅延シングルトンを破棄します -- 引数のないプライベート コンストラクターを破棄します

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 1、反射破坏双重懒汉式单例--破坏私有无参构造器
 */
public class Lazy4Reflect {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect() {}

    // 2、声明类成员变量singleton
    private static Lazy4Reflect singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect();
                }
            }
        }
        return singleton;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        Lazy4Reflect instance = Lazy4Reflect.getInstance();
        Constructor<Lazy4Reflect> constructor = Lazy4Reflect.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        Lazy4Reflect instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

 出力は次のとおりです。2 つのインスタンス メモリ アドレスは明らかに異なります。

2. リフレクションは、ダブル レイジー シングルトンを破棄します -- 引数のないプライベート コンストラクターを破棄し、ロックも破棄します

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏
 */
public class Lazy4Reflect1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect1() {
        synchronized (Lazy4Reflect1.class){
            if (singleton != null){
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect1 singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect1 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect1.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect1();
                }
            }
        }
        return singleton;
    }
}

class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect1> constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect1 instance = constructor.newInstance();
        Lazy4Reflect1 instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

3. リフレクションは、ダブル レイジー スタイルのシングルトンを破棄します -- 引数のないプライベート コンストラクター、ロック、およびフラグも破棄します

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏
 */
public class Lazy4Reflect2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect2() {
        synchronized (Lazy4Reflect2.class){
            if (isReflect = false){
                isReflect = true;
            }else {
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect2 singleton;
    private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect2 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect2.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect2();
                }
            }
        }
        return singleton;
    }
}

class Test2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 反射破坏单例
        Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");
        isReflectField.setAccessible(true);

        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect2> constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect2 instance = constructor.newInstance();
        isReflectField.set(instance,false);
        Lazy4Reflect2 instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

7. static 内部クラス (スレッドセーフ; 一般的に使用)

/**
 * 7、静态内部类(线程安全;常用)
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}

この方法は、空腹の中国の方法で採用されたメカニズムに似ていますが、異なります。

どちらも、インスタンスの初期化時にスレッドが 1 つだけであることを保証するために、クラス ロードのメカニズムを使用します。

違いは、空腹の中国のメソッドは、Singleton クラスが読み込まれている限りインスタンス化され、Lazy-Loading の影響を受けないことと、静的内部クラス メソッドは、Singleton クラスが読み込まれたときにすぐにインスタンス化されず、必要なときにインスタンス化されることです。 getInstance メソッドが呼び出されると、SingletonInstance クラスがロードされ、Singleton のインスタンス化が完了します。

クラスの静的プロパティは、クラスが初めてロードされるときにのみ初期化されるため、ここでは JVM を使用してスレッドの安全性を確保します. クラスが初期化されると、他のスレッドは入ることができなくなります. 

利点: スレッドの安全性、遅延読み込み、高効率

短所: リフレクションによってシングルトン パターンが壊れる可能性がある 

反射破壊

/**
 * 7、静态内部类(线程安全;常用)
 * 
 * 可以通过反射破坏
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}


class TestStaticInner{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        StaticInnerClass instance = StaticInnerClass.getInstance();
        Constructor<StaticInnerClass> constructor = StaticInnerClass.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        StaticInnerClass instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

8、列挙 (スレッド セーフ; 逆シリアル化を防ぐことができます; 強くお勧めします!!!)

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

利点: スレッド セーフ、シングルトン モードを破壊するリフレクションを心配する必要がない

短所: 列挙型クラスは大量のメモリを占有します

コンストラクターのソース コード

public final class Constructor<T> extends Executable {
    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
}

リフレクションを使って破壊してみる

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

class TestEnumSingleton{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        EnumSingleton instance = EnumSingleton.INSTANCE;

        // java.lang.NoSuchMethodException
        //Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);

        // 注:通过 jad 反编译得到 有参构造
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // java.lang.NoSuchMethodException
        EnumSingleton instance1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance1);
    }
}

jad 逆コンパイルによってパラメータ化された構造体を取得する 

  

 エラーを直接報告する

シングルトンモードはレイジーマンスタイル、ハングリーマンスタイル、ハングリーマンスタイルの同期ロック、ダブルチェックロック、静的内部クラス、列挙型クラスに分かれており、それぞれに長所と短所があり、用途に合わせて選択できます。プロジェクトの実際のニーズ シングルトン パターン

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/127401583