注册式单例(两种:枚举、容器)
枚举单例模式
public enum SingletonEnum {
/**
* 单例
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
}
反编译:
public static final SingletonEnum INSTANCE;
private Object data;
private static final SingletonEnum $VALUES[];
static
{
INSTANCE = new SingletonEnum("INSTANCE", 0);
$VALUES = (new SingletonEnum[] {
INSTANCE
});
}
反编译发现INSTANCE被静态代码块初始化了,是一种饿汉式单例。
测试(这里不放代码和原理):
- 开2000条线程去getInstance(),没有线程安全问题,保证单例。
- 序列化反序列化之后看是否为同一对象,结果是的。
- 对enum进行反射,失败。
- 不能保证直接对data进行反射,对data进行反射还是可以实例化的。
这里主要还是利用了enum的一些特性,从反编译就可以看出,enum是天生单例且线程安全的,这种线程安全是jvm层面的,因为jvm保证类加载是线程安全的。其次,枚举序列化的时候用的是类名和class来找到唯一枚举对象,所以序列化也能保证单例。最后反射enum的时候也有保证,不论是你反射用了无参的,还是enum(String.class,int.class),底层都会被阻拦,因为如果是enum类型就会抛出异常,这是jdk层面的保护。但是并不能保证非枚举类型进行反射咯,比如上面的data还是可以被反射出来的。
容器式单例
public class ContainerSingleton {
/**
* 构造器私有
*/
private ContainerSingleton() {
}
private static final Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
spring也是这么干的,AbstractAutowireCapableBeanFactory抽象类;