[Java] 单例设计模式详解

模式定义:保证一个类只有一个实例,并且提供一个全局访问点,时一种创建型模式

使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池

单例设计模式的实现

1.懒汉模式:延迟加载,只有真正用到的时候才去做实例化

public class LazySingletonTest {
    
    
    public static void main(String[] args) {
    
    
        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);
    }
}
class LazySingleton{
    
    

    private static LazySingleton instance;

    public static LazySingleton getInstance(){
    
    
        if (instance == null){
    
    
            instance = new LazySingleton();
        }
        return instance;
    }
}

这是懒汉模式最基本的概念,就是什么时候需要了再什么时候创建

但是这样设计可能会有线程安全问题,同一时间两个访问就可能会new出来两个instance
最简单的解决思路就是给getInstance上锁

    public synchronized static LazySingleton getInstance(){
    
    
        if (instance == null){
    
    
            instance = new LazySingleton();
        }
        return instance;
    }

新出现的问题就是方法每次都加锁,完全没有必要,属于是性能浪费
那么可以进行一个锁的延迟

    public synchronized static LazySingleton getInstance(){
    
    
        if (instance == null){
    
    
        	synchronized (LazySingleton.class){
    
    
        		if(instance == null){
    
    	//还是防止第一次初始化两个线程竞争
        			instance = new LazySingleton();
        		}
        	}
        }
        return instance;
    }

从字节码来说,JIT或者CPU会再instance = new LazySingleton();这行代码的初始化和引用赋值进行重排序
在引用复制而没有初始化的中间又来了一个线程访问,发现instance已经赋值了,他就会直接拿到那个静态的instance而不是进入if,这就会导致他虽然拿到了,但是拿到的instance是空的,因为没有初始化
可以用volatile防止指令重排序来解决这个问题

	private volatile static LazySingleton instance;

2.饿汉模式:

类加载的初始化阶段就完成了类的初始化,本质上就是借助于jvm类的加载机制,保证实例的唯一性

public class hungrySingletonTest {
    
    
    public static void main(String[] args) {
    
    
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton instance1 = HungrySingleton.getInstance();
        System.out.println(instance1 == instance);
    }
}

class HungrySingleton{
    
    
    private static HungrySingleton instance = new HungrySingleton();
    //不允许外部进行实例化
    private HungrySingleton(){
    
    }
    public static HungrySingleton getInstance(){
    
    
        return instance;
    }
}

饿汉模式赋值是在类加载的初始化阶段完成的,而类加载的是在真正的使用对应的类时,才会触发初始化

3. 静态内部类模式:

本质也是通过类加载的机制实现的懒加载的方式

class InnerClassSingleton{
    
    
    private static class InnerClassHolder{
    
    
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    
    }
    public static InnerClassSingleton getInstance(){
    
    
        return InnerClassHolder.instance;
    }
}

与饿汉模式稍有差异,饿汉模式是在类加载的时候就直接把instance初始化了,而静态内部类的方式下,不调用getInstance()这个方法,是不会对里面的静态内部类InnerClassHolder进行加载,instance也不会初始化,所以也是一种懒加载
他的本质上也是利用类的加载机制来保证类的线程安全

防止反射攻击

这种单例的设计模式都是可能通过反射来获取新的实例对象的,如

public class InnerClassSingletonTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
    	//通过反射获取他的构造器,然后通过构造器getInstance一个对象
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();

		//正常方法获取对象
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        //返回flase
		System.out.println(instance == innerClassSingleton);
    }
}

class InnerClassSingleton{
    
    
    private static class InnerClassHolder{
    
    
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    
    }
    public static InnerClassSingleton getInstance(){
    
    
        return InnerClassHolder.instance;
    }
}

下面给出防范措施,如饿汉模式和静态内部类的模式下,可以在构造器内进行判断是否有过初始化,如果有就说明这是通过反射等非正常手段获取的instance的,然后抛出异常

	class InnerClassSingleton{
    
    
    private static class InnerClassHolder{
    
    
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    
    
        if (InnerClassHolder.instance != null) {
    
    
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
    
    
        return InnerClassHolder.instance;
    }
}

很明显,懒汉模式没有办法防止这种反射

枚举类型

首先我们看一下反射中constructor的newInstance()方法源码

    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
    
    
        if (!override) {
    
    
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    
    
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        //-------!!!!!!!!!!!!!!!!!!!!---------
        //由这行代码可知,如果使用反射访问的类是个枚举类,那么就会抛出异常
        //由这个思路可以用枚举来防止反射攻击
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
    
    
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

下面是用枚举设计的最基础的单例模式

public class EnumSingletonTest {
    
    
    public static void main(String[] args) {
    
    
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance1 == instance);
    }
}
enum EnumSingleton{
    
    
    INSTANCE;
    public void print(){
    
    
        System.out.println(this.hashCode());
    }
}

我们通过翻看字节码文件可以看到
字节码文件下EnumSingleton的构造器
enum类其实是继承了java.lang.Enum,
可以看到Enum的构造器
Enum类的构造器
所以要访问他的话,反射里面也要拿到这个构造器
最后根据构造器来newInstance出来
反射枚举类之后newInstance的结果图
可以看到,很好的防止反射进行攻击

序列化的单例

实际工作中,我们需要类可以序列化(如implements Serializable),之后进行数据传输
一个Instance在写入又读取的操作以后,读取不会走构造器,所以就会生成一个新的实例,破坏了单例的形式

//在静态内部类那个类做了个测试
public class InnerClassSingletonTest {
    
    
    public static void main(String[] args) throws Exception {
    
    

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
        oos.writeObject(instance);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton instance1 = (InnerClassSingleton)ois.readObject();
		ois.close();
        System.out.println(instance1 == instance); 	//输出false
    }
}

class InnerClassSingleton implements Serializable {
    
    
    private static class InnerClassHolder{
    
    
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    
    
        if (InnerClassHolder.instance != null) {
    
    
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
    
    
        return InnerClassHolder.instance;
    }
}

来看看官方给出的解决方案
在这里插入图片描述
意思就是实现一个特殊的方法,然后返回你想要的那个单例就行了

public class InnerClassSingletonTest {
    
    
    public static void main(String[] args) throws Exception {
    
    

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
        oos.writeObject(instance);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton instance1 = (InnerClassSingleton)ois.readObject();

        System.out.println(instance1 == instance);
    }
}

class InnerClassSingleton implements Serializable {
    
    
    static final long serialVersionUID = 42L;	//序列化版本号
    private static class InnerClassHolder{
    
    
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
    
    
        if (InnerClassHolder.instance != null) {
    
    
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
    
    
        return InnerClassHolder.instance;
    }
	//实现的readResolve方法
    Object readResolve() throws ObjectStreamException{
    
    
        return InnerClassHolder.instance;
    }
}

PS:切记要写个版本号,不然报错捏

猜你喜欢

转载自blog.csdn.net/qq_67548292/article/details/131950191