单例模式
1.饿汉
public class Singleton {
/**
* 饿汉
*/
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
2.懒汉
public class Singleton {
/**
* 懒汉
*/
private static Singleton INSTANCE = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
3.静态内部类
public class Singleton {
/**
* 静态内部类
*/
private Singleton(){}
private static class InternalDemo{
private final static Singleton INSTANCE = new Singleton();
}
public Singleton getInstance(){
return InternalDemo.INSTANCE;
}
}
4.双重加锁
public class Singleton {
/**
* 双重加锁
*/
private volatile static Singleton INSTANCE;
private Singleton(){};
public static Singleton getInstance(){
if(null == INSTANCE){
synchronized(Singleton.class){
if(null == INSTANCE){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
5.枚举
public enum Singleton {
/**
* 枚举单例
*/
INSTANCE;
public Singleton getInstance(){
return INSTANCE;
}
}
6.总结
实现单例模式的三个特点:构造方法私有化,实例化的变量引用私有化,获取实例的方法共有。
除了枚举外,别的方法都存在序列化问题和被反射攻击的问题。
序列化问题: 单例类实现了java.io.Serializable
接口,就可能被序列化和反序列化,比如用Stream
的方式来读取,readObject()
方法每次都会新建一个实例对象,这个新建的实例不同于该类初始化时创建的实例。
反射问题: 单例类由不同的类装载器装入,就有可能存在多个单例类的实例。
JDK源码对枚举做了特殊处理,反射在通过newInstance()
创建对象时,会检查该类是否EMUM
修饰,如果是则抛出异常,反射失败。
获取类的Class对象的方法:
getClass(): 作用于对象,在编译期加载;
Object obj = new Object();
obj.getClass();
.class: 作用于类,在编译期加载;
Object obj = Object.class;
Class.forName(“”): 在运行时动态加载,在loadClass后必须初始化,目标对象的static代码块会被初始化;
Class.forName("java.lang.Object");
ClassLoader.loadClass(“”): 目标对象被装载后不会进行链接,不会去执行该类的静态代码块。
ClassLoader.getSystemClassLoader().loadClass("java.lang.Object");