应用最广的模式——单例模式
抽象工厂模式的定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源。例如:创建一个对象需要消耗的资源过多,比如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,只有在第一次调用StaticInternalClassSingleton的**getSingleton()**方法时才会初始化。这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所有优点它都占了,所有,就它了。
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。