Javaシングルトンの7つのテストプラクティス

目次

1.空腹の男モード。アクティブタイプはラフすぎます。

2.レイジーマンモードのスレッドは安全ではありません

3.怠惰な男ロックモードのスレッドはまだ安全ではありません

4.二重検出ロック命令の並べ替え

5.二重検出と揮発性の必要性のロック

6.静的内部クラスのパッシブ作成(推奨)

7.静的コードブロック

8.列挙

総括する


シングルトン:1つのプロセスに存在できるオブジェクトは1つだけです。

 

1.空腹の男モード。アクティブタイプはラフすぎます。


/**
 * @author :jiaolian
 * @date :Created in 2021-01-10 21:25
 * @description:饿汉单例测试
 * @modified By:
 * 公众号:叫练
 */
public class HungerSignletonTest {
    //类初始化会创建单例对象
    private static HungerSignletonTest signleton = new HungerSignletonTest();

    private HungerSignletonTest(){};

    public static HungerSignletonTest getInstance() {
        return signleton;
    }

    public static void main(String[] args) {
        //三个线程测试单例,打印hashcode是否一致
        new Thread(()->{System.out.println(HungerSignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(HungerSignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(HungerSignletonTest.getInstance().hashCode()); }).start();
    }
}

hungry manモードでは、上記のプログラムコードなどのオブジェクトをアクティブに作成します。JDK1.8環境では、メインスレッドが3つのスレッドを開始して、HungerSignletonTestインスタンスのハッシュコードが同じオブジェクトであるかどうかを取得します。テスト結果を以下に示します。下の図。すべてのハッシュコードは、プログラムが1つしかないことを証明するために一貫しています。インスタンス。空腹のシングルトンは、クラスの初期化で事前にオブジェクトを作成します。短所オブジェクトの作成が時期尚早であると、事前にメモリリソースを消費する必要があり、シングルトンオブジェクトを使用する場合はそれらを作成する必要があります。レイジーモードのコードを見てみましょう。

image.png

 

2.レイジーマンモードのスレッドは安全ではありません

/**
 * @author :jiaolian
 * @date :Created in 2021-01-10 21:39
 * @description:懒汉设计模式测试
 * @modified By:
 * 公众号:叫练
 */
public class LazySignletonTest {
    private static LazySignletonTest signleton = null;
    private LazySignletonTest(){};

    public static LazySignletonTest getInstance() {
        if (signleton == null) {
            /*try {
                //创建对象睡2秒
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            signleton = new LazySignletonTest();
        }
        return signleton;
    }

    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
    }
}

レイジーモードでは、シングルトンを使用してgetInstance()メソッドを呼び出してオブジェクトを作成する必要があります。問題はないようです。上記のコメントステートメントを手放し、オブジェクトの作成中に2秒間スリープすると、考えられる結果を次の図に示します。3つのスレッドgetハッシュコードの値が異なり、signletonオブジェクトがシングルトンではないことを示しています。遅延の場合、すべてのスレッドがif条件ステートメントに入るので、次の状況になります。短所:スレッドセーフではないため、ロックを追加する必要があります。getInstance()メソッド、public static synchronized LazySignletonTest getInstance()を変更し、synchronized変更、プログラムを実行すると、3つのスレッドが同じハッシュコードを出力します。それをテストして、完了です。まだ終わってないの?注意深く見ると、同期された変更が方法であり、ロック力は比較的大きくなります。インスタンスオブジェクトを作成するときにロックを追加するだけで済みます。私たちのように、究極のコード最適化を追求するプログラマーは最後まで「バックル」する必要があります。 。同期で変更されたシングルトンコードブロックを見てみましょう

image.png

3.怠惰な男ロックモードのスレッドはまだ安全ではありません

/**
 * @author :jiaolian
 * @date :Created in 2021-01-10 21:39
 * @description:懒汉设计模式测试
 * @modified By:
 * 公众号:叫练
 */
public class LazySignletonTest {
    private static LazySignletonTest signleton = null;
    private LazySignletonTest(){};

    public static LazySignletonTest getInstance() {
        if (signleton == null) {
            /*try {
                //创建对象睡2秒
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            synchronized (LazySignletonTest.class) {
                signleton = new LazySignletonTest();
            }
        }
        return signleton;
    }

    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
    }
}

シングルディテクションロックモードは、サインレットが空ではないと判断し、初めてロックしてオブジェクトを作成します。問題ないようです。上記のコードコメントを手放し、オブジェクトの作成中に2秒間スリープすると、可能な結果を​​次の図に示します。各スレッドによって取得されるハッシュコード値は異なり、signletonオブジェクトがシングルトンではないことを示しています。なぜそうなのですか?3つのスレッドがThread.sleep(2000)を呼び出すため、オブジェクトが作成される前にブロックされます。これは、3つのスレッドがsignletonがnullに等しいとすでに判断しているため、すべて新しいインスタンスを作成するためです。OK、この場合、同期同期コードブロックに別の判断追加して、問題がないことを確認できます。これは、二重検出とロックを備えた単一のケースであり、非常に古典的なインタビューの質問です。コードを二重検出およびロックメカニズムに変更しますが、絶対確実ですか?以下のコードを見てみましょう!事実は言葉よりも雄弁です!

image.png

 

4.二重検出ロック命令の並べ替え

/**
 * @author :jiaolian
 * @date :Created in 2021-01-10 21:39
 * @description:懒汉设计模式测试
 * @modified By:
 * 公众号:叫练
 */
public class LazySignletonTest {
    private static LazySignletonTest signleton = null;
    private LazySignletonTest(){};

    public static LazySignletonTest getInstance() {
        if (signleton == null) {
            synchronized (LazySignletonTest.class) {
                if (signleton == null) {
                    signleton = new LazySignletonTest();
                }
            }
        }
        return signleton;
    }

    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(LazySignletonTest.getInstance().hashCode()); }).start();
    }
}

ロックモードは、サインレットが初めて空ではないと判断し、ロックを使用してオブジェクトを作成します。多くのテストの後、ハッシュコードの結果は一貫しており、プロセスにオブジェクトが1つしかないことを示しており、何もないようです。それは間違っています!本当?次に、上記のコードに対して別の詳細なテストを実行します

image.png

 

 

5.二重検出と揮発性の必要性のロック

import java.util.concurrent.CountDownLatch;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-10 21:39
 * @description:没有volatile修饰单例对象测试!
 * @modified By:1.堆分配空间 2.初始化构造函数 3.地址指向
 * 公众号:叫练
 */
public class VolatileLockTest {
    private static VolatileLockTest signleton = null;
    public int aa;

    private VolatileLockTest(){
        aa = 5;
    };

    public static VolatileLockTest getInstance() {
        if (signleton == null) {
            synchronized (VolatileLockTest.class) {
                if (signleton == null) {
                    signleton = new VolatileLockTest();
                }
            }
        }
        return signleton;
    }

    public static void reset() {
        signleton = null;
    }

    public static void main(String[] args) throws InterruptedException {
        //循环三个线程测试单例
        while (true) {
            CountDownLatch start = new CountDownLatch(1);
            CountDownLatch end = new CountDownLatch(100);
            for (int i=0;i<100; i++) {
                Thread thread = new Thread(()->{
                    try {
                        //多线程同时等待
                        start.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取单例,如果锁aa等于0相当于是new 指令重排序了;
                    if (VolatileLockTest.getInstance().aa != 5) {
                        System.out.println("线程终止");
                        System.exit(0);
                    }
                    end.countDown();
                });
                thread.start();
            }
            start.countDown();
            end.await();
            reset();
        }
    }
}

上記のコードに示すように、メインプログラムでは、複数のスレッドが無限ループで作成され、シングルトンオブジェクトになります。変数 "aa"は、新しいVolatileLockTest();オブジェクトが再配置されるかどうかをテストするために定義されます。通常、JVMでは次の3つのステップで実行されます。

  1. スペースを割り当てます。ヒープ上のスペースを開きます。
  2. コンストラクターの割り当てを実行します。VolatileLockTestのプライベートコンストラクターを呼び出します。
  3. オブジェクトへの参照をポイントします。サインレットを新しいオブジェクトにポイントします。

実行効率のためにjvm2、3を再配置し、実行順序は1-> 3-> 2になります。複数のスレッドが同時に発生する場合、「aa」は5に等しくない場合があります。これは、命令が再配置された場合、Inマルチスレッドの場合、プロセスには複数のインスタンスがあり、シングルトンの状況には適合しません。正しい状況は、インスタンス変数をvolatileで変更することです。これにより、命令の再配置が禁止される可能があります。つまり、新しい命令は次のようになります。 1-> 2-> 3順次実行に従って、これはオブジェクト変数を変更するためのvolatileの必要性です。volatileの特性の詳細については、記事「揮発性、同期された可視性、秩序、原子コードの証明( Basic Hardcore) "、実用的なコードがたくさん含まれています!

 

6.静的内部クラスのパッシブ作成(推奨)

/**
 * @author :jiaolian
 * @date :Created in 2021-01-11 15:49
 * @description:静态内部类单例模式
 * @modified By:
 * 公众号:叫练
 */
public class InnerClassSingleton {
    private InnerClassSingleton(){};

    public static InnerClassSingleton getInstance() {
        return InnerClass.innerClassSingleton;
    }

    private static class InnerClass {
        private static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }

    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(InnerClassSingleton.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(InnerClassSingleton.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(InnerClassSingleton.getInstance().hashCode()); }).start();
    }
}

静的内部クラスは、シングルトンを使用してgetInstance()メソッドを呼び出してオブジェクトを作成する必要があります。複数のテストの後、3つのスレッドによって取得されたハッシュコード値は同じであり、signletonオブジェクトがシングルトンであることを証明します。シンプルで安全なコードが推奨されるソリューションです。

 

7.静的コードブロック

/**
 * @author :jiaolian
 * @date :Created in 2021-01-11 16:05
 * @description:静态代码块初始单例
 * @modified By:
 * 公众号:叫练
 */
public class StaticSingleton {

    private static StaticSingleton staticSingleton;

    //静态代码块初始单例对象.
    static {
        staticSingleton = new StaticSingleton();
    }


    //private构造函数
    private StaticSingleton(){};

    //获取单例静态方法
    public static StaticSingleton getInstance() {
        return staticSingleton;
    }


    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(StaticSingleton.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(StaticSingleton.getInstance().hashCode()); }).start();
        new Thread(()->{System.out.println(StaticSingleton.getInstance().hashCode()); }).start();
    }
}

上記のコードに示されているように、静的コードブロックはクラスの初期化中に呼び出され、 3つのスレッドは同時にシングルトンオブジェクトを取得します。ハッシュコード値の繰り返しテストは常に一貫しており、静的コードブロックがシングルトンを実現できます。短所:オブジェクトのアクティブな作成は、「空腹の男」シングルトンに似ています。

 

 

8.列挙


import java.sql.Connection;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-11 16:39
 * @description:枚举单例
 * @modified By:
 * 公众号:叫练
 */
enum DatabaseFactory {
    connectionFactory;
    private Connection connection;
    private DatabaseFactory(){
        System.out.println("初始化连接Connection");
        //初始化连接 省略TODO
    }
    public Connection getConnection() {
        return connection;
    }

    public static void main(String[] args) {
        //三个线程测试单例
        new Thread(()->{System.out.println(DatabaseFactory.connectionFactory.getConnection()); }).start();
        new Thread(()->{System.out.println(DatabaseFactory.connectionFactory.getConnection()); }).start();
        new Thread(()->{System.out.println(DatabaseFactory.connectionFactory.getConnection()); }).start();
    }
}

上記のコードに示されているように、これは「空腹の男」シングルトンに似ています。列挙型を初期化すると、デフォルトでコンストラクターがロードされます

 

総括する


7つのシングルトンの使用法について話し、次のように要約しました。

  • 静的プライベート変数
  • プライベートコンストラクター
  • public static getsingletonメソッド

また、シンプルで効率的なシングルトンの実装には、静的内部クラスメソッドお勧めします。また、シングルトンメソッドの二重検出とロックの実装に焦点を当て、内部のピットを詳細に説明し、原因を説明します。と効果。それがあなたに役立つならば、好きで、注意を払ってください。私の名前はリアン[公式アカウント]です。電話して練習します。

 

残りの問題:ダブルチェックして、揮発性のロックの必要性では、多くのテストの後に、それはAAが5に等しくないことをテストされていなかった、滞在してください

image.png

おすすめ

転載: blog.csdn.net/duyabc/article/details/112506690