JAVA単一例(Singleton)で実現されるいくつかの方法(マルチスレッドセキュリティ)

4539 ワード

主に二つに分けられます。
  • 直接初期化
  • 遅延初期化
  • 直接初期化
    直接初期化final静的なメンバー
    スレッド安全:JVMはfinal静的なメンバーが一回だけ初期化されることを保証します。
    公有静的メンバは、finalドメインであり、直接メンバを参照して、単一の例を取得する。
    /**
     *         final 
     *           
     */
    public class Singleton1 {
        public static final Singleton1 INSTANCE = new Singleton1();
    
        /**
         *        private,     new      
         */
        private Singleton1() {}
    
        public void someMethod() {}
    
        public static void main(String[] args) {
            Singleton1.INSTANCE.someMethod();
        }
    }
    
    公有のメンバは静的工場法であり、この方法により単一の例を取得する。
    柔軟性が提供され、APIを変更しない前提で、このクラスは単一の例であるべきかどうかの考え方を変えることができる。例えば、この方法を呼び出すスレッドごとにユニークな例を返す(ThreadLocal)。
    /**
     *              ,         
     */
    public class Singleton2 {
        private static final Singleton2 INSTANCE = new Singleton2();
        /**
         *        private,     new      
         */
        private Singleton2() {}
    
        /**
         *            
         * @return Singleton2   
         */
        public static Singleton2 getInstance() { return INSTANCE; }
    
        public void someMethod() {}
    
        public static void main(String[] args) {
            Singleton2.getInstance().someMethod();
        }
    }
    
    単一要素を含むエニュメレート・タイプ(enum)
    エニュメレート・タイプの性質によって保証されるエニュメレーション定数INSTANCEは、唯一の例である。
    /**
     *              
     *                   
     */
    public enum EnumSingleton {
        INSTANCE;
        public void someMethod() { /** .... */}
    
        public static void main(String[] args) {
            EnumSingleton.INSTANCE.someMethod();
        }
    }
    
    遅延初期化
    静的工場法に直接synchronizedを加える。短所:呼び出しごとにスレッドオーバーヘッドがあります。
    /**
     *        
     */
    public class LazyInitSingleton1 {
        private static LazyInitSingleton1 INSTANCE;
        /**
         *        private,     new      
         */
        private LazyInitSingleton1() {}
    
        /**
         *              
         *        
         *                      
         * @return LazyInitSingleton1   
         */
        public synchronized static LazyInitSingleton1 getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new LazyInitSingleton1();
            }
    
            return INSTANCE;
        }
    
        public void someMethod() {}
    
        public static void main(String[] args) {
            Singleton2.getInstance().someMethod();
        }
    }
    
    lazy initialization holder classモードです。(「Effective Java」71条参照:遅延初期化を慎む
    利点:同期方法のオーバーヘッドを回避する。getInstanceが第1回起動されたとき、SingletonHolder.fieldを読み出し、SingletonHolder類が初期化される。
    /**
     * lazy initialization holder class   
     *          
     */
    public class LazyInitSingleton2 {
        private static class SingletonHolder {
            static final LazyInitSingleton2 field = computeFieldValue();
            private static LazyInitSingleton2 computeFieldValue() {
                return new LazyInitSingleton2();
            }
        }
    
        private LazyInitSingleton2() {}
        public static LazyInitSingleton2 getInstance() {
            return SingletonHolder.field;
        }
    }
    
    二重検出で同期方法のオーバーヘッドを低減します。
    INSTANCEは、volatile修饰子を使用して、JVMのリアルタイムコンパイラによるINSTANCE = new LazyInitSingleton3()操作の命令の並び替えを防止します。
    /**
     *      ,    ,        
     */
    public class LazyInitSingleton3 {
        /**
         *   :    volatile    
         */
        private static volatile LazyInitSingleton3 INSTANCE;
        private LazyInitSingleton3() {}
        public static LazyInitSingleton3 getInstance() {
            //          ,   INSTANCE       ,
            //      ,      
            if (INSTANCE == null) {
                //       (        getInstance,          INSTANCE == NULL)
                synchronized (LazyInitSingleton3.class) {
                    //           ,
                    //             INSTANCE,
                    //               INSTANCE != NULL,     
                    if (INSTANCE == null) {
                        INSTANCE = new LazyInitSingleton3();
                    }
                }
            }
    
            return INSTANCE;
        }
    }
    
    どうしてINSTANCEはvolatile修繕符を使いますか?
    JVMでnew操作は次の3つのことをしました。
  • は、newが必要なオブジェクトLazyInitSingleton3にメモリ空間
  • を割り当てる。
  • は、LazyInitSingleton3の構成関数を呼び出してオブジェクト
  • を初期化する。
  • は、ステップ1で割り当てられたメモリ空間
  • にINSTANCEを向ける。
    JVMには命令並び替えの最適化があるので、上記第2ステップと第3ステップの順序は保証できない(1−3−3または1−3−2)。ステップが1−3−2であれば、スレッドAが第3ステップまで実行されたと仮定するが、第2ステップはまだ実行されていない。このとき、スレッドB呼び出しgetInstance()INSTANCEが非空であることを発見する(初期化されていない)。INSTANCEに直接戻り、その後、スレッドBがINSTANCEに対して動作すると、エラーが発生する可能性がある(オブジェクトが初期化されていないため)。volatile修飾子は、命令の並び替えの最適化を防止し、実行順序は1−2−3であることを保証する。