单例模式的概念
在JVM中某个类的对象仅能有一个,而且自行实例化并向整个系bai统du提供这个实例。
如何创建单例对象
饿汉模式
/**
* 饿汉模式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton1 {
public static final MySingleton1 INSTANCE = new MySingleton1();
private MySingleton1(){};
}
私有化构造函数,则证明所有的类不能再通过new 关键字来实例化单例类。而且 static final
关键字表示在JVM加载的时候就实例化了一个对象。此种方式的优点是线程安全,调用效率高。缺点是如果没有地方用到这个单例对象,就很浪费资源。
懒汉模式
/**
* 懒汉模式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton2 {
private static MySingleton2 INSTANCE;
private MySingleton2(){};
//保证线程安全
public synchronized static MySingleton2 getInstance(){
if(INSTANCE == null){
INSTANCE = new MySingleton2();
}
return INSTANCE;
}
}
懒汉模式则实现了调用的时候再实例化,但是为了防止并发,则在获取实例的方法上加上了 synchronized
关键字确保线程安全。这样做的优点是节省空间,但是牺牲了加载效率。
静态内部类方式
升级版的写法则是使用静态内部类的方式
/**
* 单例,静态内部类方式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton3 {
private MySingleton3(){};
private static class MySingletonInstance{
private static final MySingleton3 INSTANCE = new MySingleton3();
}
public static MySingleton3 getInstance(){
return MySingletonInstance.INSTANCE;
}
}
这种方式算是比较完美的,线程安全,延迟加载都做到了。
防止反射攻击生成对象
虽然我们禁用了new 关键字创建对象,但是如果有“特权平台”获取构造器,再通过构造器生成对象,则单例模式就被破坏了
饿汉模式
例如看个例子,以上面的饿汉模式为例:
结果输出,可见并不是同一个对象
所以我们可以在构造方法中加入一个判读,如果对象已经被创建则抛出异常
/**
* 饿汉模式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton1 {
public static final MySingleton1 INSTANCE = new MySingleton1();
private MySingleton1(){
if (INSTANCE != null){
throw new IllegalStateException();
}
};
}
懒汉模式
如果是懒汉模式,则上面的做法还是存在风险的,如果反射发生在getInstance()之前,那么 INSTANCE != null
判断不成立,则不会抛异常,所以还是可以通过反射生成对象。
静态内部类方式
/**
* 单例,静态内部类方式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton3 {
private MySingleton3(){
if (MySingletonInstance.INSTANCE != null){
throw new IllegalStateException();
}
};
private static class MySingletonInstance{
private static final MySingleton3 INSTANCE = new MySingleton3();
}
public static MySingleton3 getInstance(){
return MySingletonInstance.INSTANCE;
}
}
反序列化创建实例
如果类实现了Serializable接口,则类可以通过反序列化来实例化,单例模式为了防止反序列化目标类的对象,则需要在目标类中添加一个 private Object readResolve()方法
以饿汉模式为例
/**
* 饿汉模式
* @author zhy
* @date 2021/2/2316:33
*/
public class MySingleton4 implements Serializable{
private static final MySingleton4 INSTANCE = new MySingleton4();
private MySingleton4(){
if (INSTANCE != null){
throw new IllegalStateException();
}
}
public MySingleton4 getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
添加readResolve() 方法的原因就是JVM在反序列化组装对象的时候,就会在目标对象中合成一个readResolve()方法,通过readResolve()返回一个对象。我们这里重写了直接返回 单例对象,防止反序列化对单例模式进行破坏。