单例模式的介绍
单例模式是应用很广泛的一种模式,也是最简单的一种模式。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。
例如:创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
五种单利模式的实现方法
实现单例模式主要有几个关键点:
- 构造方法不对外开放,一般为private
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其在多线程的情况下
- 确保单例类对象在反序列化时不会重新构建对象
1. 饿汉式
/**
* 单例模式:饿汉模式
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
饿汉模式:在加载类的时候就进行了实例化,提前占用了资源
2. 懒汉式
/**
* 单例模式:懒汉模式
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance() {
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
懒汉模式:单例对象只有在需要被使用时才会被实例化,但是第一次加载时需要及时进行实例化,反应稍慢,而且在使用getInstance() 获取单例对象时,都会进行同步,造成不必要的资源浪费
3. 双重检查锁定(DCL)
/**
* 单例模式:双重检查锁定(Double Check Lock)
*/
public class DCLSingleton {
private volatile static DCLSingleton instance = null;
private DCLSingleton(){}
public static DCLSingleton getInstance(){
if(instance == null){
synchronized(DCLSingleton.class){
if (instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
}
DCL:需要加 volatile 关键字
4. 静态内部类
/**
* 单例模式:静态内部类
*/
public class StaticSingleton {
private StaticSingleton(){}
public static StaticSingleton getInstance(){
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder{
private static final StaticSingleton instance = new StaticSingleton();
}
}
静态内部类:当第一次加载StaticSingleton类时并不会初始化 instance ,只有在第一次使用 getInstance() 时才会导致 instance 被初始化。第一次调用getInstance 方法会导致虚拟机加载SingletonHolder类,这种方式不仅保证线程安全,也能保证单例对象的唯一性,同时也延迟了单例对象的实例化。
5. 枚举类型
public enum EnumSingleton {
INSTANCE;
}
枚举:默认枚举实例的创建是线程安全的,并且在任何情况下他都是一个单例
问题
通过序列化可以将一个单例的实例对象写到磁盘,然后在读回来,从而有效地获得一个实例,即使构造函数是私有的,反序列化时依旧可以通过特殊的途径去创建类的一个新的实例,相当于调用了该类的构造方法。反序列化操作提供了一个很特别的方法,类中具有一个私有的readResolve() 方法,这个方法可以让开发人员控制对象的反序列化。如果前面四种方式需要杜绝单例对象在被反序列化时被重新生成对象,那就需要实现 Serializable 接口,加入 readResolve() 方法(将单例对象返回,而不是重新生成一个对象),但是对于枚举来说,是没有这个问题的。
private Object readResolve() throws ObjectStreamException{
return instance;
}