设计模式(一)— 单例模式

应用最广的模式——单例模式

单例模式的定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式的使用场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源。例如:创建一个对象需要消耗的资源过多,比如IO操作、数据库操作等,这时就要考虑使用单例模式。

单例模式的示例

1.饿汉单例模式

public class EHanSingleton {
    private static final EHanSingleton eHanSingleton = new EHanSingleton();

    private EHanSingleton() { }

    public static EHanSingleton getEHanSingleton() {
        return eHanSingleton;
    }
}

饿汉式单例模式,在没有使用的时候就初始化了对象,占用资源,并且是线程不安全的,不好,不推荐使用。

2.懒汉单例模式

public class LanHanSingleton {
    private static LanHanSingleton lanHanSingleton;

    private LanHanSingleton() { }

    public static synchronized LanHanSingleton getLanHanSingleton() {
        if (lanHanSingleton == null) {
            lanHanSingleton = new LanHanSingleton();
        }
        return lanHanSingleton;
    }
}

懒汉式单例模式,在getLanHanSingleton()方法中添加了synchronized关键字,使其变成同步方法,在多线程情况下保证对象的唯一性。

  • 优点:只有在使用时才会被实例化,在一定程度上节约了资源。
  • 缺点:第一次加载的时候需要及时进行实例化,反应稍慢,最大的问题是每次调用getLanHanSingleton()方法都要进行同步,造成不必要的开销。所以,这种单例模式也不建议使用。

3.DCL单例模式

DCL,Double Check Lock,双重检查锁定

public class DCLSingleton {
    private volatile static DCLSingleton dclSingleton = null;

    private DCLSingleton() { }

    public static DCLSingleton getDclSingleton() {
        if (dclSingleton == null) {
            synchronized (DCLSingleton.class) {
                if (dclSingleton == null) {
                    dclSingleton = new DCLSingleton();
                }
            }
        }
        return dclSingleton;
    }
}

getDclSingleton()方法中,进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层则是在null的情况下创建实例。细心的童鞋应该注意到了,在定义dclSingleton时,多了一个volatile关键字,这是干啥用的?它的作用,是来保证DCL单例模式的正确性,虽然volatile会稍微影响那么一丢丢的性能。请看下面解释:

DCL失效问题

上面的例子,假设线程A执行到 dclSingleton = new DCLSingleton()语句,这句代码最终会被编译成多条汇编指令,大致做了3件事情:
(1)给DCLSingleton的实例分配内存;
(2)调用DCLSingleton()的构造函数,初始化成员字段;
(3)将 dclSingleton 对象指向分配的内存空间(此时 dclSingleton 就不是null了)。

好,重点来了,快拿小本本记起来。Java编译器允许处理器乱序执行,以及JDK1.5之前Java的内存模型JMM中Cache、寄存器到主内存回写顺序规定,上面的第2步和第3的顺序是无法保证的。哦吼~,如果是在线程A先执行了第3步,再切换到线程B,这时候dclSingleton已经不是null,但是第2步还没执行呢,这时候线程B要是去使用的话,就会出错了。这就是DCL失效问题。

解决办法
JDK1.5之后,调整了JVM,具体化了volatile关键字。按照上面例子的写法,在定义dclSingleton时,加volatile关键字,就可以保证dclSingleton对象每次都是从主内存中读取,从而确保了程序的正确性。

4.静态内部类单例模式

public class StaticInternalClassSingleton {

    private StaticInternalClassSingleton() { }

    public static StaticInternalClassSingleton getSingleton() {
        return SingletonHolder.staticInternalClassSingleton;
    }

    private static class SingletonHolder {
        private static final StaticInternalClassSingleton staticInternalClassSingleton = new StaticInternalClassSingleton();
    }
}

静态内部类单例模式,在第一次加载StaticInternalClassSingleton类时并不会初始化staticInternalClassSingleton,只有在第一次调用StaticInternalClassSingletongetSingleton()方法时才会初始化。这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所有优点它都占了,所有,就它了。

6.使用容器实现单例模式

public class SingletonManager {
    private static Map<String, Object> singletonMap = new HashMap<>();

    private SingletonManager() { }

    public static void registerService(String key, Object instance) {
        if (!singletonMap.containsKey(key)) {
            singletonMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return singletonMap.get(key);
    }
}

使用容器来管理单例,将多个单例注册到一个统一的管理类中,使用key获取对应的对象,这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低用户的使用成本,隐藏了具体实现,降低了耦合度。

小结

优点

1.由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建、销毁时,而且创建或销毁是性能又无法优化,单例模式的优势就非常明显了。

2.由于单例模式只生成一个实例,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

3.单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

4.单例模式可以在系统设置全局的访问点,优化和共享资源访问。

缺点

1.单例模式一般没有接口,扩展很困难。

2.单例对象如果持有Context,那么很容易引发内存泄漏,传递给单例对象的Context最好是Application Context。

猜你喜欢

转载自blog.csdn.net/kavenka/article/details/82526806