玩转单例模式

290 阅读2分钟

1、饿汉式单例

代码:

public class Hungry {
    //私有构造函数,保证其他类不能实例化此类
    private Hungry(){
    }
    //自己创建一个类的实例化
    private final static Hungry HUNGRY = new Hungry();
    //提供公共的访问方式
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

但是这样的单例模式存在一定问题。

若是在这个类中有一些数组,而一上来就给他创建了实例,则会造成空间的浪费。

public class Hungry {
    
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    //私有构造函数,保证其他类不能实例化此类
    private Hungry(){
    }
    //自己创建一个类的实例化
    private final static Hungry HUNGRY = new Hungry();
    //提供公共的访问方式
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

2、懒汉式单例

public class LazyMan {
    //私有构造函数,保证其他类不能实例化此类
    private LazyMan(){

    }
    //自己创建一个类的实例化,并使用volatile关键字保证原子性
    private volatile static LazyMan lazyMan;
    //创建get方法返回一个实例
    public static LazyMan getInstance(){
        if (lazyMan == null){
            synchronized (LazyMan.class){
                if (lazyMan == null) {
                  lazyMan = new LazyMan();
                }
             }
        }
        return lazyMan;
    }
}

这样的单例模式也是有一定问题的。若是没有volatile关键字:

由于lazyMan = new LazyMan();不是一个原子性操作,他会经过三步操作。

  1. 分配内存空间
  2. 执行构造函数,初始化对象
  3. 把这个对象指向这个空间

这三步没有顺序性,执行的步骤可能是1、2、3,也可能是1、3、2。

若是第一个线程执行1、3、2执行到3的时候,第二条线程进入,判断LazyMan不为空。直接返回,则会有其他问题。

3、静态内部类实现单例模式

public class Holder {
    //构造器私有
    private Holder(){
    }
    //提供get方法
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    //静态内部类
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

4、反射破解单例模式

4.1、代码

public static void main(String[] args) throws Exception {
	//正常获取LazyMan对象
	LazyMan instance = LazyMan.getInstance();
	//通过反射获取LazyMan对象
	Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
	declaredConstructor.setAccessible(true);
	LazyMan instance2 = declaredConstructor.newInstance();
	System.out.println(instance);//LazyMan@45ee12a7
	System.out.println(instance2);//LazyMan@330bedb4
}

4.2、阻止反射破解单例模式

在无参构造中添加判断:

private LazyMan(){
	synchronized (LazyMan.class){
		if (lazyMan==null ){
			throw new RuntimeException("请勿使用反射破坏单例模式");
		}
	}
}

5、使用枚举类型实现单例模式

写一个枚举类:

public enum  EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

尝试用反射破解

public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
	EnumSingle instance = EnumSingle.INSTANCE;
	Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
	declaredConstructor.setAccessible(true);
	EnumSingle instance2 = declaredConstructor.newInstance();
	System.out.println(instance == instance2);
}

报错: