一.定义
单例模式,类都是单例的,一个类最多只有一个实例对象。
二.实现
为实现单例模式,类的构造器须设置为私有的,然后通过方法取获取类的实例
1.懒汉式
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
简单的一种方式,第一次调用getInstance()方法时产生唯一实例
优:简单
缺:在多线程下,第一次初始化时可能多个线程同时判断出instance为null,产生了多个实例,破坏单例
2.懒汉式(多线程)
当涉及多线程的时候,懒汉式就可能会产生多个实例。可以直接在getInstance()方法上加synchronize,但效率太低,一般用以下方式
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
第一次调用getInstance()方法时产生唯一实例
优:适用于多线程
缺:可能出现问题,详细如下
instance = new Singleton() 并不是原子操作,可以分为以下几步:
1.为对象分配内存
2.初始化一个Singleton对象
3.将instnce引用指向对象地址
由于重排序的原因,可能产生1-2-3或1-3-2的执行顺序,若3先执行再执行2,jvm去初始化对象,在初始化的过程中,另一个新的线程调用getInstance()方法,然后判断第一个instance==null时,为false,直接返回instance,而返回的instance对象是一个未初始化完成的对象,使用时可能会出错。
另外还有个线程可见性问题,一个线程为instance初始化后,可能这个初始化对另一线程还不可见,再次初始化,这样就初始化了多次,破坏单例
解决方法:将instance变量设成violate类型,禁止重排序,保证变量对所有线程的可见性
3.饿汉式
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
在类初始化时产生唯一实例
优:简单
缺:在类初始化时就产生了唯一实例,过早地浪费资源
4.静态内部类
public class Singleton {
private Singleton() {}
public static class SingletonInit {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonInit.instance;
}
}
优:实现了延迟加载
缺:多产生的了一个静态类
5.枚举
枚举类型是单例,直接调用Sinleton.INSTANCE即可得到实例
public enum Singleton {
INSTANCE;
}
优:简单,安全性高,能保证单例
缺:在类初始化时就产生了唯一实例,过早地浪费资源
三.应对各种破坏单例的情况
1.反射调用私有构函数
反射可以绕过私有检查机制调用私有构造函数,破坏单例,所以可以在构造函数中加判断
private Singleton() {
if (instance != null) {
throw new RuntimeException("不能再次实例化");
}
}
2.抵御clone攻击
clone会产生多个不同实例,破坏单例,只要类实现Cloneable接口,再覆盖clone方法便可避免这种情况
@Override
public Object clone() throws CloneNotSupportedException {
return getInstance();
}
3.反序列化
给类实现Serializable接口,便可序列化。但反序列化会破坏单例模式,演示代码如下
String filePath = "xxx";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
Singleton originSingleton = Singleton.getInstance();
objectOutputStream.writeObject(originSingleton);
File file = new File(filePath);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton singleton = (Singleton) objectInputStream.readObject();
System.out.println(singleton == originSingleton);
结果为false,两个实例不一样
解决方法:在单例类中添加readResolve()方法
private Object readResolve() {
return getInstance();
}
再用上面的代码反序列化,得到的实例将是一样的。具体为什么可以研究objectInputStream.readObject()方法实现,在类有readResolve方法时,反序列化将是调用readResolve()得到实例对象
上述为了保证单例,要写好多东西,太麻烦了。直接使用枚举实现单例,便可免去这些麻烦,为保证单例枚举类自己都做好了