单例模式

作者: DjangoW | 来源:发表于2018-08-11 11:56 被阅读2次

    单例模式是在一个应用(Application)中某个类(Class)有且仅有一个实例(Instance)存在。

    相当于一个全局对象,方便对整个系统的行为进行协调,比如服务器的配置信息由一个单例对象保存。

    单例模式又可细分为 懒汉方式(lazy-initialization)和饿汉方式,懒汉方式是只有需要的时候才会去创建,饿汉方式则是在Application初始化或者.class类文件加载阶段直接创建。

    饿汉模式(不存在线程不安全的问题):

    public class Singleton {
        private final static Singleton INSTANCE = new Singleton();
        // Private constructor suppresses 
        private Singleton() {}
        // default public constructor
        public final static Singleton getInstance() {
            return INSTANCE;
        }
    }
    

    final staticINSTANCE只有在Singleton的成员变量或者函数(非final static literal成员变量)第一次被使用的时候才会触发实例化。在Singleton类加载到JVM的时候INSTANCE被放到JVM方法区常量池中,其值new Singleton()则只是存了Singleton类以及constructor方法的符号引用,实例化被JVM推迟。

    一般的懒汉模式(非线程安全):

    public class Singleton {   
        private static Singleton INSTANCE = null;   
        private Singleton() {}   
         public static Singleton getInstance() {       
            if(INSTANCE == null){  INSTANCE = new Singleton();  }
        }
        return INSTANCE;   
    }
    

    试想在单核模式下当两个线程t1和t2一同访问getInstance()函数,t1先检查INSTANCE是否是null之后被挂起,t2检查INSTANCEnull之后创建Singleton的instance并挂起,t1被唤醒再次创建Singleton的instance,造成了数据的覆盖。

    一般的懒汉模式(线程安全):

    public class Singleton {   
        private static Singleton INSTANCE = null;   
        private Singleton() {}   
         public static synchronized Singleton getInstance() {       
            if(INSTANCE == null){  INSTANCE = new Singleton();  }
        }
        return INSTANCE;   
    }
    

    直接给getInstance()Class级别函数加互斥锁,每个访问该函数的线程都得先拿到该Class的monitor锁。但是这样过多的加锁解锁消耗资源不够efficiency。

    Double Check的懒汉模式(线程安全):

    public class Singleton {
        private static volatile Singleton INSTANCE = null;
        // Private constructor suppresses
        private Singleton() {}
        //thread safe and performance  promote
        public static  Singleton getInstance() {
            if(INSTANCE == null){
                synchronized(Singleton.class){
                    if(INSTANCE == null){
                        INSTANCE = new Singleton();
                      }
                  }
            }
            return INSTANCE;
        }
    }
    

    相比前一个实现,每次getInstance都先检查是否是null,如果不是null则不执行加锁解锁的操作,所以更高效。

    volatile关键字的作用是使变量内存可见(visible to memory),防止代码重排序(确保happens-before原则)。

    synchronized关键字则会为Singleton.class加monitor锁,每个调用该段代码的Thread必须先拿到Singleton.class的monitor锁才能执行该段代码,而同一时间只能有一个Thread持有该monitor锁。

    静态内部类的实现:

    public final class Singleton{
        public static class SingletonHandler{
            private final static Singleton INSTANCE=new Singleton();
        }
        public final static Singleton getInstanc(){
            return SingletonHandler.INSTANCE;
        }
    }
    

    内部类StingletonHandler在编译之后会生成一个单独的.class文件:Singleton$SingletonHandler.classSingleton.class被加载方法区之后保留的只是SingletonHandlerSingletonHandler.INSTANCE的qualified name(全限定名的符号引用symbolic reference),只有在调用getInstance方法时JVM才会通过classloader将Singleton$SingletonHandler.class加载到方法区,在heap区创建Singleton的实例,并将符号引用(symbolic reference)转换为直接引用(direct reference)。
    以上加载过程都是线程安全的,所以该实现高效&自动线程安全。

    使用场景:当Singleton这个类有提供其他很多static的函数的话,通过SingletonHandler可以实现懒加载(仅在需要用到这个instance的时候才去加载内部类并创建Singleton的instance)。

    Enum实现:

    public Enum Singleton{
        INSTANCE;
        //functions to add
    }
    

    Decompile之后的代码是:

    public final class Singleton extends Enum
    {
        private Singleton(String s, int i)
        {
            super(s, i);
        }
    
        public static Singleton[] values()
        {
            Singleton asingleton[];
            int i;
            Singleton asingleton1[];
            System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
            return asingleton1;
        }
    
        public static Singleton valueOf(String s)
        {
            return (Singleton)Enum.valueOf(structure/proxy/Singleton, s);
        }
    
        public static final Singleton INSTANCE;
        private static final Singleton ENUM$VALUES[];
    
        static
        {
            INSTANCE = new Singleton("INSTANCE", 0);
            ENUM$VALUES = (new Singleton[] {
                INSTANCE
            });
        }
    }
    

    和饿汉模式很像,相比而言优点是写法更简单,而且自动提供了Serialisable序列化(其他单例实现的序列化则需手动implements Serialisable接口实现序列化)。

    关于JVM类加载流程在这里先简单写一下:

    1. loading -> 找.class文件(TYPE)并加载入JVM
    2. linking -> 分三部分
      2.1. verification -> 检查引入TYPE文件正确性
      2.2. preparation -> 给class变量分配内存(在方法区)并赋值default value:(boolean:false, int:0, reference:null)
      2.3. resolution -> 将symbolic reference转为direct reference (通常延后触发)
    3. initialization -> 触发代码提供的赋值语句(通常延后触发)

    欢迎批评指正,谢谢!

    Reference

    https://zh.wikipedia.org/wiki/单例模式
    https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
    https://stackoverflow.com/questions/16771373/singleton-via-enum-way-is-lazy-initialized

    相关文章

      网友评论

        本文标题:单例模式

        本文链接:https://www.haomeiwen.com/subject/tfzrbftx.html