JAVAでの一例モード

6204 ワード

Javaでは、単一のモードの実現方法は以下のとおりです.
1.餓漢モード
餓漢モードは、最初から静的オブジェクトが作成され、その後もオブジェクトが存在します.このモードではスレッドセキュリティの問題はありません.
package com.dhb.builder.singleton;

public class Singleton1 {
    
    private static Singleton1 instance = new Singleton1();

    private Singleton1() {
    }
    
    public static Singleton1 getInstance() {
        return Singleton1.instance;
    }

}


2.怠け者モード
餓漢モードでは、簡単な実現が利点です.しかし、instanceはSingleton 1がロードされるとstaticが存在する静的メソッド領域に作成されるという問題がある.データベース接続プールを実装するなど、この方法ではデータベースの接続リソースを作成し、実際のシステムでは使用しません.これは資源の浪費をもたらした.そのため、このような状況に対して、それに対応する怠け者パターンが現れた.すなわち、最初はオブジェクトを作成せず、必要に応じてnewします.
package com.dhb.builder.singleton;

public class SingletonDemo1 {
    
    private static SingletonDemo1 instance = null;
    
    private SingletonDemo1() {
        
    }

    /**
     *         
     * @return
     */
    public static SingletonDemo1 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo1();
        }
        return instance;
    }
}


これはみんなが考えている最もよく使う怠け者のパターンの書き方です.しかし問題が来て、上記のモードはマルチスレッドの場合はスレッドが安全ではありません!すなわち,2つのスレッドがある場合,getInstance()と同時にinstanceの値がnullであると判断する.この場合、複数のインスタンスが作成されます.上記の問題を解決するために、ロックを導入しました.
package com.dhb.builder.singleton;

public class SingletonDemo2 {

    private static SingletonDemo2 instance = null;

    private SingletonDemo2() {
        
    }

    /**
     *       ,      
     * @return
     */
    public static synchronized SingletonDemo2 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

上記の方法では、スレッドセキュリティの問題は確かに解決されましたが、より悪い問題をもたらしました.それは、リクエストごとにロックがかかっていることです.これにより、パフォーマンスに深刻な影響を及ぼします.より良い方法は、二重検査メカニズムを採用することです.
package com.dhb.builder.singleton;

public class SingletonDemo3 {

    private static SingletonDemo3 instance = null;

    private SingletonDemo3() {
        
    }

    /**
     *         ,  synchronized    
     * @return
     */
    public static  SingletonDemo3 getInstance() {
        if(instance == null)
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        return instance;
    }
}


上記の単一の例は、クラスの初期化には時間がかかるという問題があり、同時に2つのスレッドがgetInstanceメソッドに同時に進入し、最初のスレッドがロックされた後、2番目のスレッドが空ではないと判断した場合、instalceを直接使用し、このとき最初のスレッドがSingletonDemo 3オブジェクトに対してまだインスタンス化されていない場合、そのオブジェクト内部に時間のかかる参照が存在する場合、データベース接続の場合、2番目のスレッドで使用されるオブジェクトが不完全になります.空のポインタが表示されます.だからもっと良い書き方はvolatileを加えることです.happen-beforeの原則を保証します.
package com.dhb.builder.singleton;

public class SingletonDemo4 {

    private volatile static SingletonDemo4 instance = null;

    private SingletonDemo4() {
        
    }

    /**
     *         ,  synchronized    
     * @return
     */
    public static SingletonDemo4 getInstance() {
        if(instance == null)
            synchronized (SingletonDemo4.class) {
                if (instance == null) {
                    instance = new SingletonDemo4();
                }
            }
        return instance;
    }
}


このようにして、単一のモードが完全に解決されます.上記の方法は比較的に冗長で、もっと良い解決方法があるかどうか、幸いにも『effective java』という本を読んだことがあって、単例に対してもっと良い解決方法があります.
3.より良い解決方法
1つ目の方法は、静的内部クラスを使用することです.
package com.dhb.builder.singleton;

import java.util.stream.IntStream;

public class SingletonHolder {

    private SingletonHolder() {

    }
    private static class InstanceHolder{
        private final static SingletonHolder INSTANCE = new SingletonHolder();
    }

    public static SingletonHolder getInstance() {
        return InstanceHolder.INSTANCE;
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1,100).forEach(i -> new Thread(String.valueOf(i)){
            @Override
            public void run() {
                System.out.println(SingletonHolder.getInstance());
            }
        }.start());
    }
}


上記の方法の実行結果:
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6
com.dhb.builder.singleton.SingletonHolder@55c1b7a6

SingletonHolderクラスは一度だけインスタンス化されていることがわかります.この方法は内部クラスを巧みに利用し,簡単なコードで単一例を実現し,スレッドセキュリティである.
方法2:『effective java』にはもっと簡単な書き方があります.それは列挙です.『effective java』の著者が最も推奨する方法でもある.
package com.dhb.builder.singleton;

import java.util.stream.IntStream;

public class SingletonEnum {

    private SingletonEnum() {

    }

    private enum Singleton {
        INSTANCE;

        private final SingletonEnum instance;

        Singleton() {
            instance = new SingletonEnum();
        }

        public SingletonEnum getInstance() {
            return instance;
        }
    }

    public static SingletonEnum getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        IntStream.rangeClosed(1,100).forEach(i -> new Thread(String.valueOf(i)){
            @Override
            public void run() {
                System.out.println(SingletonEnum.getInstance());
            }
        }.start());
    }
}

上記の方法の実行結果:
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6
com.dhb.builder.singleton.SingletonEnum@55c1b7a6

Javaでは,列挙は天然に単例モードを実現した.その構造方法は一度だけインスタンス化されます.