之前在学校的时候只学了双重校验锁的方式,面试的时候答的还觉得不错,殊不知还有更多的方式以及双重校验锁也存在一些问题。今天来学习一下除了双重校验锁的其他方式。
1.双重校验锁
先回顾一下双重校验锁
public class Singleton1 {
//私有化构造器
private Singleton1(){};
private static volatile Singleton1 instance ;
//静态方法获取实例
public static Singleton1 getInstance(){
//双重检查
if(instance == null){
synchronized (Singleton1.class){
if(instance==null){
instance = new Singleton1();
}
}
}
return instance;
}
}
2.内部静态类
public class Singleton2 {
private Singleton2(){};
private static class SingletonHolder{
private static final Singleton2 INSTANCE = new Singleton2();
}
public static Singleton2 getInstance(){
return SingletonHolder.INSTANCE;
}
}
静态内部类单例模式中实例由内部类创建,由于
JVM
在加载外部类的过程中
,
是不会加载静态
内部类的
,
只有内部类的属性
/
方法被调用时才会被加载
,
并初始化其静态属性。静态属性由于被
static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
第一次加载
Singleton
类时不会去初始化
INSTANCE
,只有第一次调用
getInstance
,虚拟机加
载
SingletonHolder 并初始化INSTANCE
,这样不仅能确保线程安全,也能保证
Singleton
类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任
何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
3.枚举类
public enum Singleton3 {
INSTANCE;
}
上述单例模式除了枚举类都是可以被“破坏”的,破坏的方式有序列化和反射。
所谓序列化就是把对象写到本地文件里,然后再读出来,读两次会返回两个对象。
反射就是获取Singleton类的字节码对象(.class),然后获取无参构造器(这个是私有的,所以要设置一下setAccessible),通过构造器对象创建两个Singleton对象,比较它们是否是同一个。
解决方案:
解决序列化破坏单例可以再Singleton里加入一个方法
public Object readResolve(){
return SingletonHolder.instance;//对应静态内部类
}
在反序列化的时候会执行这个方法,从而返回原来的单例对象,否则会重新生成一个。
解决反射的方法是在Singleton类的私有构造器中加入一个判断方法,类中加入一个flag属性
public class Singleton2 {
private static boolean flag = false;
private Singleton2(){
synchronized (Singleton2.class){
if(flag){
throw new RuntimeException("单例构造器禁止反射调用");
}
flag=true;
}
};
......
}
这样在第二次构造类的时候会抛异常,且加锁了效率也会下降
学习的知识来源于黑马的网课,感谢黑马