美文网首页我爱编程
01 单例模式(Singleton Pattern)

01 单例模式(Singleton Pattern)

作者: 智行孙 | 来源:发表于2018-05-24 17:23 被阅读0次

    一句话概括:限制类的实例化,全局最多只有一个该类的实例。

    单例模式是四大模式之一,概念很简单,但实现起来会有诸多问题。

    Java Singleton

    • 单例模式限制类的实例化,确保只有一个实例在JAVA虚拟机中。
    • 单例模式必须提供一个全局访问来获取类的实例。
    • 单例模式用于日志记录(logging),驱动程序对象(drivers objects),缓存(caching)和线程池(thread pool)等。
    • Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。

    Java Singleton Pattern

    实现Singleton模式有多种方法,但是它们都有以下共同的概念。

    • 构造函数私有化(private constructor),以限制被其它类初始化。
    • 类的内部提供一个私有的静态变量来保存该类的全局唯一实例。
    • 提供一个公共的静态方法返回该类的唯一实例,这是外部获取该单例类实例的全局唯一访问点。

    在下面的章节中,我们将学习Singleton模式实现的不同方法以及各个实现中涉及的问题。

    Eager initialization

    Eager initialization是指Singleton类的实例在类被加载时创建,这是创建单例类最简单的方法。

    package com.journaldev.singleton;
    
    public class EagerInitializedSingleton {
    
        //在类被加载时就初始化该类的实例
        private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
    
        //防止其它成员初始化该类
        private EagerInitializedSingleton(){}
    
        public static EagerInitializedSingleton getInstance(){
            return instance;
        }
    }
    

    若你的单例类没有使用太多的资源,这种方法比较合适。但在大多数情况下,Singleton类是为文件系统,数据库连接等资源创建的。
    除了调用getInstance方法获取实例外,我们应该避免被其它类实例化。另外注意,这种方法在实例化时没有办法提供任何异常处理选项。

    Lazy Initialization

    在全局初次使用该实例的时候进行初始化
    示例代码:

    package com.journaldev.singleton;
    
    public class LazyInitializedSingleton {
    
        private static LazyInitializedSingleton instance;
    
        private LazyInitializedSingleton(){}
    
        //在初次调用的时候进行初始化
        public static LazyInitializedSingleton getInstance(){
            if(instance == null){
                instance = new LazyInitializedSingleton();
            }
            return instance;
        }
    }
    

    上面的两个实现在单线程环境下工作良好,但是当涉及到多线程时,如果多个线程进入getInstance()方法的if循环内部,则会导致问题(尤其是在类的实例化需要一定时间时经常发生)。 它会破坏单例模式,两个线程都会得到单例类的不同实例。 接下来我们来讨论创建线程安全的单例类的方法。

    Thread Safe Singleton

    创建线程安全单例类的更简单方法是使全局访问方法同步,以便一次只有一个线程可以执行此方法。

    package com.journaldev.singleton;
    
    public class ThreadSafeSingleton {
    
        private static ThreadSafeSingleton instance;
    
        private ThreadSafeSingleton(){}
    
        public static synchronized ThreadSafeSingleton getInstance(){
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
            return instance;
        }
    }
    

    以上方法能在多线程下工作良好提供了线程安全的方式,但是牺牲了性能,因为增加了同步方法(synchronized),其实我们仅仅需要用在可能会导致单独创建实例的前几个线程(参考:Java多线程相关)。为了避免每次额外的开销,我们可以使用双重检查锁定(double-checked locking),在if条件中使用synchronized块进行附加检查,以确保只创建一个singleton类实例。

    public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
        if(instance == null){
            synchronized (ThreadSafeSingleton.class) {
                if(instance == null){
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
    

    Bill Pugh Singleton Implementation

    在Java 5之前,Java内存模型存在很多问题,以上方法在某些情况下会失败,因为太多线程试图同时获取Singleton类的实例。 所以Bill Pugh提出了一种使用内部静态帮助类来创建Singleton类的另一种方法。 Bill Pugh Singleton的实现是这样的:

    package com.journaldev.singleton;
    
    public class BillPughSingleton {
    
        private BillPughSingleton(){}
    
        private static class SingletonHelper{
            private static final BillPughSingleton INSTANCE = new BillPughSingleton();
        }
    
        public static BillPughSingleton getInstance(){
            return SingletonHelper.INSTANCE;
        }
    }
    

    注意private static class SingletonHelper私有静态内部类,当单例类被加载时,SingletonHelper类没有被加载到内存,仅当有人调用了getInstance方法时,这个私有类才会有加载并创建单例类的实例。

    这是Singleton类最广泛使用的方法,因为它不需要同步。我在很多项目中都使用这种方法,并且很容易理解和实施。

    Using Reflection to destroy Singleton Pattern

    用反射来破坏单例模式,反射可以破坏上述所有单例模式实现,我们来看个例子:

    package com.journaldev.singleton;
    
    import java.lang.reflect.Constructor;
    
    public class ReflectionSingletonTest {
    
        public static void main(String[] args) {
            EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
            EagerInitializedSingleton instanceTwo = null;
            try {
                Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
                for (Constructor constructor : constructors) {
                    //Below code will destroy the singleton pattern
                    constructor.setAccessible(true);
                    instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(instanceOne.hashCode());
            System.out.println(instanceTwo.hashCode());
        }
    
    }
    

    当你运行上述测试代码,你会发现两个instance的hashCode是不同的,破坏了单例模式。反射非常强大,在大量的类库里被用到,例如Spring和Hibernate.

    Enum Singleton

    为了克服Reflection的这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保任何枚举值在Java程序中仅实例化一次。 由于Java Enum值是全局访问的,单例也是全局可访问的。 缺点是枚举类型有点不灵活; 例如,它不允许延迟初始化。

    package com.journaldev.singleton;
    
    public enum EnumSingleton {
    
        INSTANCE;
    
        public static void doSomething(){
            //do something
        }
    }
    

    Serialization and Singleton

    有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便我们可以将它的状态存储在文件系统中,并在之后的某个时间点取回它。 这是一个实现Serializable接口的小型单例类。

    package com.journaldev.singleton;
    
    import java.io.Serializable;
    
    public class SerializedSingleton implements Serializable{
    
        private static final long serialVersionUID = -7604766932017737115L;
    
        private SerializedSingleton(){}
    
        private static class SingletonHelper{
            private static final SerializedSingleton instance = new SerializedSingleton();
        }
    
        public static SerializedSingleton getInstance(){
            return SingletonHelper.instance;
        }
    }
    

    上面的序列化单例类的问题是,只要我们反序列化它,它就会创建一个新的类实例。 让我们看一个简单的程序。

    package com.journaldev.singleton;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    public class SingletonSerializedTest {
    
        public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
            SerializedSingleton instanceOne = SerializedSingleton.getInstance();
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                    "filename.ser"));
            out.writeObject(instanceOne);
            out.close();
    
            //deserailize from file to object
            ObjectInput in = new ObjectInputStream(new FileInputStream(
                    "filename.ser"));
            SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
            in.close();
    
            System.out.println("instanceOne hashCode="+instanceOne.hashCode());
            System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
    
        }
    
    }
    

    上面程序的输出结果是

    instanceOne hashCode=2011117821
    instanceTwo hashCode=109647522
    

    很明显破坏了单例模式,避免这种情况的方法是在单例类中提供一个readResolve()方法的实现。

    protected Object readResolve() {
        return getInstance();
    }
    
    

    之后你再运行之前那段代码就会发现,两个hashCode的值一样了。

    相关文章

      网友评论

        本文标题:01 单例模式(Singleton Pattern)

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