设计模式--01.单例模式

在程序设计中,需要保证一个只有一个对象实例,就是所谓的单例模式。在java中,有很多单例模式的实现,这篇博客是对这几种单例模式的优缺点进行分析和优化:

饿汉模式


所谓饿汗模式,就是优先创建对象,对象在类加载的时候就已经创建好了,具体的代码如下:


    private static Singleton01 instance = new Singleton01();

    public static Singleton01 getInstance() {
        return instance;
    }

`

饿汗模式优点是代码简单,由于是在类加载的时候就已经创建好了对象,所以不存在线程安全的问题。

缺点是:有的类在没有使用的时候就已经创建了对象,产生了多余的垃圾对象。


懒汉模式


懒汉模式是针对饿汉模式的一个优化,类只有在使用的时候才会进行实例化:

    private static Singleton02 instance;
    //懒汉模式
    //简单,但是线程不安全
    public static Singleton02 getInstance() {
        if (instance == null) {
            instance = new Singleton02();
        }
        return instance;
    }

懒汉模式的实例在使用的时候才去创建,但是创建的过程是线程不安全的,在多线程的环境下存在多个实例的现象。


double check模式


doublie check模式优化了懒汉模式的线程安全的问题,使用锁来保证当前只有一个线程创建实例。

   private static Singleton03 singleton03;

    //加锁,线程安全,但是效率慢
    public synchronized static Singleton03 getInstance() {
        if (singleton03 == null) {
            singleton03 = new Singleton03();
        }
        return singleton03;
    }

上面的代码足够保证了线程的安全,但是多线程的场景下会存在一个线程工作,其他线程盲等的现象,所以再为了优化这个问题,再创建进入锁之前再次进行检查, 使用volatile关键字保证变量在多线程中是共享的,代码如下:

private volatile static Singleton05 singleton05;
    //double check 性能最好
    public static Singleton05 getInstance() {
        if (singleton05 == null) {
            synchronized (Singleton05.class) {
                if (singleton05 == null) {
                    singleton05 = new Singleton05();
                }
            }
        }
        return singleton05;
    }

虽然doublecheck的模式,继承了懒汉模式的优点,也不会有线程安全的问题,但还是会存在线程盲等的现象,优化只能降低发生的概率,无法完全避免。


内部类模式


内部类模式是一个比较精妙的设计,是利用了Jvm的类的加载的特点,具体代码如下:


    public static Singleton06 getInstance() {
        return Inner.INSTANCE;
    }

    static class Inner {
        final  static Singleton06 INSTANCE = new Singleton06();
    }

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式。
缺点是会产生多余的类,反射也可以破解,通过构造函数可以防止破解


枚举单例模式


枚举是《effective java》中推荐的单例模式,利用jvm的枚举类无法被反射的创建实例的特点,所以枚举类相对更加安全和健壮。具体的代码如下:

    public enum Singleton07 {
        INSTANCE;
        Singleton07() {
            System.out.println(this.getClass().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

虽然枚举模式有点很多,但是再实际开发中却用的很少,原因是枚举类的用法比较单一(不支持继承)。


使用memoize创建单例


使用Guavamemoize函数的特性, 只再第一此获取的时候才调用,可以用来实现单例模式,代码如下:

private static final Supplier<Singleton08> InstanceSuppler = Suppliers.memoize(Singleton08::new);

    public static Singleton08 getInstance() {
        return InstanceSuppler.get();
    }

使用memoize函数,可以支持多种的实例函数的调用方式,也可以制定方法的过期的时间,更为灵活,个人推荐使用memoize方式

作者

付威

发布于

2019-10-27

更新于

2020-08-10

许可协议

评论