目前主流的实现单例模式的方式有两种,一种是饿汉式,一种是懒汉式。
1、饿汉式(类被加载时立即初始化并创建唯一的实例)
* @Description 饿汉单例模式实现
* @Date 2021/12/4 9:11 下午
* @Author chenzhibin
*/
public class StarvingSingleton {
/**
* static关键字在类加载时直接new一个实例,final确保初始化后不能再改变
*/
private static final StarvingSingleton starvingSingleton = new StarvingSingleton();
/**
* 设置私有的构造函数防止客户端调用构造函数创建实例
*/
private StarvingSingleton() {
}
/**
* 提供唯一一个获取实例的方法
*
* @return
*/
public static StarvingSingleton getInstance() {
return starvingSingleton;
}
public static void main(String[] args) {
System.out.println(StarvingSingleton.getInstance());
System.out.println(StarvingSingleton.getInstance());
}
}
2、懒汉式(双重检查锁)
* @Description 懒汉式(双重检查锁)
* @Date 2021/12/4 9:22 下午
* @Author chenzhibin
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
//第一次检测
if (null == instance) {
//同步
synchronized (LazyDoubleCheckSingleton.class) {
if (null == instance) {
/**
* 可以看到生成一个对象分三步,第2,3步并没有依赖关系,可以被程序执行器任意调整,volatile关键字就是为了禁止指令重排序
* 1、分配对象内存空间 memory == allocate()
* 2、初始化对象 instance(memory)
* 3、设置instance指向刚分配的内存地址,此时instance != null
*/
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
System.out.println(LazyDoubleCheckSingleton.getInstance());
System.out.println(LazyDoubleCheckSingleton.getInstance());
}
}
可以看到在生成单例时都是同一个对象
这两种模式的实现都解决了线程安全的问题,这两种模式构造函数都设置成了private,防止客户端调用构造方法创建出另外一个实例,那有没有什么办法可以破除这层屏障么?那就是反射机制。
可以看到通过反射生成了一个新的实例。
那有没有无视反射和序列化攻击的单例呢?看下面的例子
* @Description TODO
* @Date 2021/12/4 10:43 下午
* @Author chenzhibin
*/
public class EnumStarvingSingleton {
private EnumStarvingSingleton() {
}
public static EnumStarvingSingleton getInstance() {
return Holder.HOLDER.instance;
}
private enum Holder {
HOLDER;
private EnumStarvingSingleton instance;
Holder() {
instance = new EnumStarvingSingleton();
}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class clazz = Holder.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
System.out.println(EnumStarvingSingleton.getInstance());
System.out.println(constructor.newInstance());
}
}
运行代码后发现报java.lang.NoSuchMethodException异常,找不到对应的构造函数,那是不是算防住了反射呢?
通过查看枚举的源码发现枚举带有一个带有两个参数的构造函数,一个是String,一个是int
那我们再来试一下带参数的构造函数能够获取实例么?我们来试下
发现这次报错更明显,直接告诉我们不能通过反射的形式创建枚举,说明了枚举的私有构造函数对反射的进攻是无视的,我们可以用这种方式来创建出更为安全的单例。
以上都是手敲的,如有误可留言,我会及时更正,感谢~