我们都知道在单例模式中,对构造函数进行私有化private修饰,保证了类不能使用new进行对象的实例化,但是如果使用反射获取构造函数,在进行实例化就会导致private失效。
- 作者用中文作为类名,请读者勿怪,纯属喜好,工作中是不允许的哦~~
- java初级开发如有错误请多指正。
- 在构造函数中对实例是否存在进行判断,抛出异常,用于抵御反射攻击。
1.懒汉模式
public class 懒汉 {
private static 懒汉 instance = null;
//private保证无法new出对象
private 懒汉(){
if(instance!=null){
throw new RuntimeException("不允许多例");
}
}
public static 懒汉 getInstance() throws InterruptedException {
if(instance==null){
Thread.sleep(100);
instance=new 懒汉();
}
return instance;
}
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
懒汉 a = null;
try {
a = 懒汉.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}).start();
new Thread(()->{
懒汉 a = null;
try {
a = 懒汉.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}).start();
}
}
2.饿汉
public class Hungry {
private static Hungry instance = new Hungry();
private Hungry(){
if(instance!=null){
throw new RuntimeException("不允许多例");
}
}
public static Hungry getInstance(){
return instance;
}
public static void main(String[] args) {
//为什么这里可以new 因为这个main还在hungry类中声明
Hungry instance = new Hungry();
Hungry instance1 = Hungry.getInstance();
Hungry instance2 = Hungry.getInstance();
System.out.println(instance1==instance2);
}
}
3.静态内部类
也是属于饿汉的一种,类加载时就创建实例
//利用类加载机制,饿汉的一种形式
public class 静态内部类 {
private 静态内部类(){
if(InnerClassHolder.instance!=null){
throw new RuntimeException("不允许多例");
}
}
static class InnerClassHolder{
private static 静态内部类 instance = new 静态内部类();
}
public static 静态内部类 getInstance(){
return InnerClassHolder.instance;
}
public static void main(String[] args) {
静态内部类 instance = 静态内部类.getInstance();
静态内部类 instance2 = 静态内部类.getInstance();
new Thread(()->{
静态内部类 b = null;
b = 静态内部类.getInstance();
System.out.println(b);
}).start();
System.out.println(instance==instance2);
System.out.println(instance);
}
}
4.反射攻击
//反射对单例进行创建对象,是否会变成多例
//再次执行私有化构造函数创建对象
public class 反射攻击 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, InterruptedException {
//通过在私有化构造函数中判断抛出异常抵御反射攻击
//1-------------静态内部类------被反射攻击----异常抵御成功
// //获取构造函数
// Constructor<静态内部类> declaredConstructor = 静态内部类.class.getDeclaredConstructor();
// //构造函数私有化在反射面前不值一提
// //直接获取使用权
// declaredConstructor.setAccessible(true);
// //进行实例化
// 静态内部类 instance = declaredConstructor.newInstance();
// 静态内部类 instance1 = 静态内部类.getInstance();
// System.out.println(instance1==instance);
//2-------------懒汉------被反射攻击----异常抵御失败,因为懒汉是用到才创建对象,反射攻击时还不存在对象
// //获取构造函数
// Constructor<懒汉> declaredConstructor = 懒汉.class.getDeclaredConstructor();
// //构造函数私有化在反射面前不值一提
// //直接获取使用权
// declaredConstructor.setAccessible(true);
// //进行实例化
// 懒汉 instance = declaredConstructor.newInstance();
// 懒汉 instance1 = 懒汉.getInstance();
// System.out.println(instance1==instance);
// //3-------------饿汉------被反射攻击----异常抵御成功
// //获取构造函数
// Constructor<Hungry> declaredConstructor = Hungry.class.getDeclaredConstructor();
// //构造函数私有化在反射面前不值一提
// //直接获取使用权
// declaredConstructor.setAccessible(true);
// //进行实例化
// Hungry instance = declaredConstructor.newInstance();
// Hungry instance1 = Hungry.getInstance();
// System.out.println(instance1==instance);
}
}
这里说明一下,只有懒汉模式有被攻击的风险,由于懒汉模式的实例是在第一次使用时才被创建,所以如果最开始就使用反射创建对象会出现多例。