マルチスレッドの下の単一のパターンを本当に知っていますか?

10754 ワード

1つ目(怠け者、スレッドが安全ではありません):
 
    public class Singleton {  

        private static Singleton instance;  

        private Singleton (){}  

      

        public static Singleton getInstance() {  

        if (instance == null) {  

            instance = new Singleton();  

        }  

        return instance;  

        }  

    }  

 
この書き方lazy loadingは明らかだが,致命的なのはマルチスレッドで正常に動作しないことである.
2つ目(怠け者、スレッドセキュリティ):
 
    public class Singleton {  

        private static Singleton instance;  

        private Singleton (){}  

        public static synchronized Singleton getInstance() {  

        if (instance == null) {  

            instance = new Singleton();  

        }  

        return instance;  

        }  

    }  

 
この書き方はマルチスレッドでよく機能し、lazy loadingも備えているように見えますが、残念ながら効率が低く、99%の場合同期は必要ありません.
3つ目(餓漢):
    public class Singleton {  

        private static Singleton instance = new Singleton();  

        private Singleton (){}  

        public static Singleton getInstance() {  

        return instance;  

        }  

    }  

この方式はclassloderメカニズムに基づいてマルチスレッドの同期問題を回避するが,instanceはクラスマウント時にインスタンス化され,クラスマウントの原因は様々であるが,単例モードではgetInstanceメソッドを呼び出すことが多いが,他の方式(または他の静的メソッド)がクラスマウントをもたらすとは特定できず,instanceを初期化するとlazy loadingの効果が明らかに得られない.
第四種(餓漢、変種):
 
    public class Singleton {  

        private Singleton instance = null;  

        static {  

        instance = new Singleton();  

        }  

        private Singleton (){}  

        public static Singleton getInstance() {  

        return this.instance;  

        }  

    }  

表面的には差が大きいように見えますが、実は3つ目の方法は差が少なく、クラス初期化すなわちインスタンス化instanceです.
5つ目(静的内部クラス):
 
    public class Singleton {  

        private static class SingletonHolder {  

        private static final Singleton INSTANCE = new Singleton();  

        }  

        private Singleton (){}  

        public static final Singleton getInstance() {  

        return SingletonHolder.INSTANCE;  

        }  

    }  

この方式は同様にclassloderのメカニズムを利用してinstanceを初期化する際に1つのスレッドしかないことを保証し、3つ目と4つ目の方式とは異なり(細かい違い):3つ目と4つ目の方式はSingletonクラスがマウントされている限り、instanceはインスタンス化され(lazy loading効果に達していない)、この方式はSingletonクラスがマウントされており、instanceは必ずしも初期化されていない.SingletonHolderクラスはアクティブに使用されていないため、getInstanceメソッドを呼び出すとSingletonHolderクラスのマウントが表示され、instanceがインスタンス化されます.想像してみてください.インスタンス化instanceがリソースを消費している場合、私は彼にロードを遅らせたいと思っています.一方、Singletonクラスのロード時にインスタンス化したくないです.Singletonクラスが他の場所でアクティブに使用されてロードされる可能性があることを確保できないので、この時にインスタンス化instanceは明らかに適切ではありません.この時、この方式は3番目と4番目の方式に比べて合理的に見えます.
6つ目(列挙):
 
    public enum Singleton {  

        INSTANCE;  

        public void whateverMethod() {  

        }  

    }  

この方式はEffective Javaの著者Josh Blochが提唱している方式で、マルチスレッド同期の問題を回避できるだけでなく、逆シーケンス化を防止して新しいオブジェクトを再作成することができ、強い障壁と言えるでしょうが、個人的には1.5にenumの特性が加わったため、このように書くのは疎遠に感じられ、実際の仕事では、このように書いた人はめったに見られません.
第七種類(二重チェック錠):
 
    public class Singleton {  

        private volatile static Singleton singleton;  

        private Singleton (){}  

        public static Singleton getSingleton() {  

        if (singleton == null) {  

            synchronized (Singleton.class) {  

            if (singleton == null) {  

                singleton = new Singleton();  

            }  

            }  

        }  

        return singleton;  

        }  

    }  

まとめ
2つの問題に注意してください.
1.単一のインスタンスが異なるクラス・ローダによってロードされる場合、複数の単一のクラスのインスタンスが存在する可能性があります.リモートアクセスではないと仮定します.たとえば、いくつかのservletコンテナでは、各servletに対して全く異なるクラスマウンタが使用されます.これにより、2つのservletが1つの単一のクラスにアクセスすると、それぞれのインスタンスがあります.
2.Singletonがjavaを実現する場合.io.Serializableインタフェースでは、このクラスのインスタンスがシーケンス化され、復元される可能性があります.いずれにしても、単一クラスのオブジェクトをシーケンス化し、次に複数のオブジェクトを復元すると、複数の単一クラスのインスタンスが作成されます.
最初の問題を修正する方法は、次のとおりです.
 
    private static Class getClass(String classname)      

                                             throws ClassNotFoundException {     

          ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     

          

          if(classLoader == null)     

             classLoader = Singleton.class.getClassLoader();     

          

          return (classLoader.loadClass(classname));     

       }     

    }  

2つ目の問題を修正する方法は、次のとおりです.
 
    public class Singleton implements java.io.Serializable {     

       public static Singleton INSTANCE = new Singleton();     

          

       protected Singleton() {     

            

       }     

       private Object readResolve() {     

                return INSTANCE;     

          }    

    }   

私にとって、3つ目と5つ目の方法が好きで、分かりやすくて、JVM層でスレッドセキュリティを実現しています(複数のクラスローダ環境でなければ)、一般的には3つ目の方法を使いますが、lazy loading効果を明確に実現するには5つ目の方法を使います.また、逆シーケンス化作成オブジェクトに関わる場合は列挙した方法で単例を実現してみますが、私はずっと私のプログラムがスレッドの安全であることを保証することができて、しかも私は永遠に第1種と第2種の方式を使うことができなくて、もし他の特殊な需要があれば、私は第7種の方式を使うことができて、結局、JDK 1.5二重チェックロックの問題はありません.
========================================================================
しかし、一般的には、1つ目は単例ではなく、4つ目と3つ目が1つで、計算すれば5つ目も別々に書くことができます.だから、一般的に単例は5つの書き方です.怠け者、悪漢、二重チェックロック、列挙と静的内部クラス.
このような読者がいて、一緒に勉強できて嬉しいです.