设计模式系列-单例模式

148 阅读4分钟

单例模式 顾名思义就是这个类只会创造一个唯一的实例。单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式又这样的一些特点

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。
  • 构造函数是private,从而避免了类在外部被实例化(其实可以使用反射来实例化,但是此处不进行考虑)

优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,比如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
  • 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  • 单例对象如果持有Context,那么很容易引发内存泄露,此时需要注意传给单例对象的Context最好是Application Context。

概括来说,单例模式有三种实现方式

  • 懒汉式
  • 饿汉式
  • 枚举

懒汉式

懒汉的意思就是懒加载,直到使用的时候才创建。用的时候去检查该实例是否已经创建,如果创建的话则直接返回,如果没有创建的话则创建该实例。

优点

  • 节省空间,使用的时候才创建实例

缺点

  • 传统方式线程不安全
  • 第一次调用需要做很多工作

线程不安全

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

线程安全

线程安全式的懒汉模式有以下三种实现方式

synchronized

在getInstance()方法上加上synchronized关键字来保证线程安全。

这种方法虽然保证了线程安全,但是每次都需要同步锁定,会影响性能,毕竟99%的情况下都是不需要同步的。

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

双重检查锁

在getInstance()中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,降低了每次都同步的性能损耗。

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

静态内部类

这种方式比上面的两种方式都更好,既保证了线程安全,又避免了同步带来的性能影响

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

饿汉式

饿汉的意思就是,类加载的时候就已经初始化了单例。不管用没用到都先创建该实例,保证getInstance的时候,单例已经创建好了。

优点

  • 天生线程安全
  • 第一次调用比较快,因为之前已经创建了实例

缺点

  • 浪费空间,不管使用还是没使用该实例,都创建了对象。

代码如下:

public class Singleton{
    private Singleton(){}
    private static final Singleton singleton=new Singleton();
    public static Singleton getInstance(){
        return singleton;
    }
}

枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

public enum Single{
    INSTANCE;
    public void anyMethod(){
        
    }
}

总结

一般情况下,不建议使用线程不安全懒汉式和synchronized懒方式,建议使用饿汉式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。

参考网址

如何创建一个完美的单例模式

单例模式