谈谈单例设计模式的源码应用和安全问题

一、源码应用

事实上,我们在JDK或者其他的通用框架中很少能看到标准的单例设计模式,这也就意味着他确实很经典,但严格的单例设计确实有它的问题和局限性,我们先看看在源码中的一些案例

1、jdk中的单例

jdk中有一个类的实现是一个标准单例模式(饿汉式),即Runtime类,该类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。 一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime类实例,可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    private static Version version;

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class {@code Runtime} are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the {@code Runtime} object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    
    ...
}

2、MyBatis中的单例

Mybaits中的org.apache.ibatis.io.VFS使用到了单例模式。VFS就是Virtual FileSystem的意思,mybatis通过VFS来查找指定路径下的资源。查看VFS以及它的实现类,不难发现,VFS的角色就是对更“底层”的查找指定资源的方法的封装,将复杂的“底层”操作封装到易于使用的高层模块中,方便使用者使用。

public class public abstract class VFS {
	// 使用了内部类
	private static class VFSHolder {
		static final VFS INSTANCE = createVFS();
		@SuppressWarnings("unchecked")
		static VFS createVFS() {
			// ...省略创建过程
			return vfs;
		}
	}
	
	public static VFS getInstance() {
		return VFSHolder.INSTANCE;
	}
	
	// ...
}

二、安全问题

1、反射入侵

我们可以通过反射获取私有构造器进行构造,如下代码:

@Slf4j
public class TestReflectSingleton {

    private static volatile TestReflectSingleton instance;

    private TestReflectSingleton(){}

    public static TestReflectSingleton getInstance(){
        if(instance == null){
            synchronized (TestReflectSingleton.class){
                if(instance == null){
                    instance = new TestReflectSingleton();
                }
            }
        }
        return instance;
    }

    /**
     * 测试反射入侵
     * @param args
     */
    public static void main(String[] args) {
        Class<TestReflectSingleton> cls = TestReflectSingleton.class;
        try {
            Constructor<TestReflectSingleton> constructor = cls.getDeclaredConstructor();
            // 设置为可见
            constructor.setAccessible(true);
            TestReflectSingleton instance1 = TestReflectSingleton.getInstance();
            TestReflectSingleton instance2 = constructor.newInstance();
            boolean flag = instance2 == instance1;
            log.info("flag -> {}", flag);
            log.info("flag -> {}", flag);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

在这里插入图片描述

这样输出的结果是false,证明对象不是同一个对象,我们可以在构造方法中再加一个判断对象是否为空的条件即可。代码如下:

@Slf4j
public class TestReflectSingleton {

    private static volatile TestReflectSingleton instance;

    private TestReflectSingleton(){
        if(instance != null){
            // 实例化直接抛出异常
            throw new RuntimeException("实例:【" + this.getClass().getName() + "】已经存在,该实例只被实例化一次!");
        }
    }

    public static TestReflectSingleton getInstance(){
        if(instance == null){
            synchronized (TestReflectSingleton.class){
                if(instance == null){
                    instance = new TestReflectSingleton();
                }
            }
        }
        return instance;
    }

    /**
     * 测试反射入侵
     * @param args
     */
    public static void main(String[] args) {
        Class<TestReflectSingleton> cls = TestReflectSingleton.class;
        try {
            Constructor<TestReflectSingleton> constructor = cls.getDeclaredConstructor();
            constructor.setAccessible(true);
            TestReflectSingleton instance1 = TestReflectSingleton.getInstance();
            TestReflectSingleton instance2 = constructor.newInstance();
            boolean flag = instance2 == instance1;
            log.info("flag -> {}", flag);
            log.info("flag -> {}", flag);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

直接抛出异常,这样就可以防止反射入侵啦

在这里插入图片描述

2、序列化与反序列化问题

@Slf4j
public class TestSerializeSingleton implements Serializable {
    /**
     * 简单的写个懒加载吧
     */
    private static TestSerializeSingleton instance;

    private TestSerializeSingleton() {}

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


    public static void main(String[] args) {
        String url = "DesignPatterns/src/main/resources/singleton.txt";
        // 获取单例并序列化
        TestSerializeSingleton singleton = TestSerializeSingleton.getInstance();
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fos = new FileOutputStream(url);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton);
            // 将实例反序列化出来
            fis = new FileInputStream(url);
            ois = new ObjectInputStream(fis);
            Object o = ois.readObject();
            log.info("他们是同一个实例吗?{}",o == singleton);  // return false
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(fos != null){
                    fos.close();
                }
                if(oos != null){
                    oos.close();
                }
                if(fis != null){
                    fis.close();
                }
                if(ois != null){
                    ois.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

输出结果如下:

在这里插入图片描述

readResolve()方法可以用于替换从流中读取的对象,在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在,所以在单例中添加readResolve方法:

public Object readResolve(){
     return instance;
}

再次运行代码输出结果如下:

在这里插入图片描述

三、单例存在的一些问题

1、它不支持面向对象编程

我们都知道,面向对象的三大特性是封装、继承、多态。单例将构造私有化,直接导致的结果就是,他无法成为其他类的父类,这就相当于直接放弃了继承和多态的特性,也就相当于损失了可以应对未来需求变化的扩展性,以后一旦有扩展需求,我们不得不新建一个十分【雷同】的单例。

2、极难的横向扩展

单例类只能有一个对象实例。如果未来某一天,一个实例已经无法满足我们的需求,我们需要创建一个,或者更多个实例时,就必须对源代码进行修改,无法友好扩展。

在系统设计初期,我们觉得系统中只应该有一个数据库连接池,这样能方便我们控制对数据库连接资源的消耗。所以,我们把数据库连接池类设计成了单例类。但之后我们发现,系统中有些 SQL 语句运行得非常慢。这些 SQL 语句在执行的时候,长时间占用数据库连接资源,导致其他 SQL 请求无法响应。为了解决这个问题,我们希望将慢 SQL 与其他 SQL 隔离开来执行。为了实现这样的目的,我们可以在系统中创建两个数据库连接池,慢 SQL 独享一个数据库连接池,其他 SQL 独享另外一个数据库连接池,这样就能避免慢 SQL 影响到其他 SQL 的执行。

如果我们将数据库连接池设计成单例类,显然就无法适应这样的需求变更,也就是说,单例类在某些情况下会影响代码的扩展性、灵活性。所以,数据库连接池、线程池这类的资源池,最好还是不要设计成单例类。实际上,一些开源的数据库连接池、线程池也确实没有设计成单例类。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt是一个跨平台的C++图形用户界面(GUI)应用程序开发框架,其中包含了丰富的库和工具,方便开发者创建功能强大的应用程序。在Qt中,设计模式被广泛应用,以提供可重复使用和易维护的代码结构。 Qt设计模式源码包括了各种设计模式的实现,用于解决不同类型的问题。下面我将介绍几种常用的设计模式源码: 1. 单例模式:Qt提供了QCoreApplication类,它是整个应用程序的核心单例对象。开发者可以通过调用静态函数QCoreApplication::instance()来获取唯一的实例。 2. 观察者模式:Qt中的信号和槽机制就是观察者模式的实现。对象可以通过定义信号和槽来实现消息的发送和接收,从而实现对象之间的解耦。 3. 工厂模式:Qt中的QObject类提供了对象创建和销毁的功能。开发者可以通过调用QObject::createChild()函数来创建子对象,从而实现工厂模式。 4. 迭代器模式:Qt中的容器类(如QList和QVector)提供了迭代器接口,方便开发者遍历容器中的元素。 5. 命令模式:Qt中的撤销/重做框架是命令模式的实现。开发者可以通过定义QAction对象,将操作和命令进行绑定,从而实现撤销和重做的功能。 以上仅是其中一些常用的设计模式源码,Qt还提供了更多其他的设计模式实现,例如代理模式、策略模式等。通过使用这些设计模式,开发者可以更好地组织和管理自己的代码,提高程序的可维护性和可扩展性。 ### 回答2: Qt设计模式是一套设计模式的实现和应用,用于帮助开发者在Qt框架下进行软件开发。Qt设计模式源码包含了许多常用的设计模式的实现,下面介绍几个常用的设计模式和其在Qt中的应用。 1. 观察者模式:在Qt中,观察者模式通过信号与槽机制来实现。当一个对象的状态发生变化时,会发出一个信号,其他对象可以通过连接这个信号的槽函数来观察这个状态的变化。 2. 工厂模式:Qt中的工厂模式可以通过QObject类提供的meta-object系统来实现。通过宏定义Q_OBJECT以及Q_CLASSINFO实现了类的反射功能,可以通过类名创建对象实例。 3. 单例模式:在Qt中,可以使用Q_GLOBAL_STATIC宏来定义全局的单例对象,确保整个程序中只有一个实例对象。 4. 命令模式:对于需要进行撤销、重做等操作的功能,可以使用Qt提供的QUndoCommand类和QUndoStack类来实现命令模式。 除了以上介绍的几个设计模式,Qt中还有其他的设计模式,比如策略模式、代理模式等,都有对应的源码实现。Qt设计模式源码提供了示例和范例,方便开发者习和理解各种设计模式应用。它不仅可以帮助开发者通过设计模式提高代码的可重用性和可维护性,同时也可以使代码更加清晰和易于理解。通过习和运用Qt设计模式,可以提高软件开发的效率和质量。 ### 回答3: C++ Qt设计模式源码是一套基于Qt框架的设计模式示例代码,用于帮助开发者了解和应用常见的设计模式。Qt是一种跨平台的C++应用程序开发框架,它提供了丰富的库和工具,用于开发高质量的图形用户界面和应用程序。 Qt设计模式源码包含了常见的设计模式,如单例模式、观察者模式、工厂模式、策略模式等,每个模式都有相应的示例代码和解释。这些示例代码可以作为习和参考,当我们遇到类似问题或需要使用某个设计模式时,可以直接参考这些源码,以提高开发效率。 例如,当我们需要实现一个单例模式时,可以查找Qt设计模式源码中的单例模式示例代码,并按照其中的实现方法进行开发。这样可以减少我们自己实现的复杂度和错误概率。 Qt设计模式源码还提供了对于设计模式的详细解释和使用场景,帮助开发者理解每个设计模式的优势和适用场景。这对于初者来说尤为重要,能够帮助他们掌握设计模式的概念和思想。 总之,Qt设计模式源码是一个非常有用的工具,它能够帮助开发者更好地理解和应用设计模式,提高代码质量和开发效率。无论是初者还是有经验的开发者,都可以通过习和参考这些源码来提升自己的编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值