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

news2024/11/21 0:26:15

一、源码应用

事实上,我们在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 File

System的意思,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 的执行。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1105155.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL --- 聚合查询 和 联合查询

聚合查询&#xff1a; 下文中的所有聚合查询的示例操作都是基于此表&#xff1a; 聚合函数 聚合函数都是行与行之间的运算。 count() select count(列名) from 表名; 统计该表中该列的行数&#xff0c;但是 null 值不会统计在内&#xff0c;但是如果写为 count(*) 那么 nu…

操作系统:线程同步和调度

文章目录 线程同步和调度一、实验目的二、实验要求与内容、过程与结果 总结 线程同步和调度 一、实验目的 通过创建线程、分配线程优先级和终止线程的程序设计和调试操作&#xff0c;进一步熟悉操作系统的线程概念&#xff0c;理解Windows 2000线程的生命周期。 通过对事件、…

7z压缩包如何设置加密?如果忘记密码该怎么办?

7z压缩包是压缩率最大的压缩包格式&#xff0c;当我们给文件进行压缩时&#xff0c;可能会想给压缩包进行加密&#xff0c;那么7z压缩包如何设置加密&#xff1f;加密的7z压缩包又如何解密呢&#xff1f;分享7-zip加密、解密教程。包括忘记了压缩包密码该如何解决&#xff1f; …

地理知识笔记:Haversine距离

1 介绍 Haversine距离用于计算地球上两点之间的大圆距离当考虑地球的真实曲率时&#xff0c;它特别适用于计算两个经纬度坐标之间的距离 其中&#xff1a; 2 python 实现 def haversine_distance(lat1, lon1, lat2, lon2):R 6371 # Earth radius in kilometersdlat np.r…

让千年诗酒文化生生不息,多国文艺界名家齐聚2023年国际诗酒文化艺术周盛宴

执笔 | 萧 萧 编辑 | 古利特 10月15日&#xff0c;国际诗酒文化大会第七届中国酒城泸州老窖文化艺术周在酒城泸州盛大开幕&#xff0c;文学家、艺术家、翻译家、文化学者、媒体记者等各界嘉宾与来自阿根廷、智利、哥伦比亚等十余国的诗人朋友齐聚&#xff0c;共襄年度诗酒文…

关闭mysql,关闭redis服务

1. 关闭redis服务&#xff1a; 查询redis安装目录&#xff1a; whereis redis which redis find / -name redis 关闭redis服务&#xff1a; redis-cli -h 127.0.0.1 -p 6379 auth 输入密码 shutdown 关闭redis服务 2. 关闭mysql服务&#xff1a; 查询mysql安装目录&…

基于Java的留学生交流互动论坛系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

【LeetCode】38. 外观数列

1 问题 给定一个正整数 n &#xff0c;输出外观数列的第 n 项。 「外观数列」是一个整数序列&#xff0c;从数字 1 开始&#xff0c;序列中的每一项都是对前一项的描述。 你可以将其视作是由递归公式定义的数字字符串序列&#xff1a; countAndSay(1) “1” countAndSay(n…

【linux 0.11 学习记录】一、环境配置,用Bochs输出hello world

想学习linux&#xff0c;又不知道从哪里下手&#xff0c;体系太大&#xff0c;哪块内容都很多&#xff0c;无奈下选择了linux0.11作为入口&#xff0c;本系列将是学习笔记&#xff0c;希望能坚持下去吧 环境配置 这里使用win10bochs2.7 安装bochs 官网&#xff1a;https://b…

【学习笔记】RabbitMQ-6 消息的可靠性投递2

参考资料 RabbitMQ官方网站RabbitMQ官方文档噼咔噼咔-动力节点教程 文章目录 十一、队列Queue的消息属性11.1 具体属性11.2 自动删除11.2 自定义参数11.2.1 **Message TTL** 消息存活时间11.2.2 **Auto expire** 队列自动到期时间11.2.3 **Overflow behaviour** 溢出行为11.2.4…

编程基础-C++入门到入土知识手册

C基础知识 C 语言教程一 、简介 环境设置C11 新特性文本编辑器C 编译器 二、程序结构编译 & 执行 C 程序三、 基本语法C 的令牌&#xff08;Token&#xff09;1分号 ;2注释3标识符4关键字5 C 中的空格 四、数据类型整数类型浮点类型void 类型 C primer Plus黑马C语言简介C语…

创建node、vue、以及@vuecli 和 vue-cli 的区别

创建node、vue、以及vue/cli 和 vue-cli 的区别 创建vue的五种方法 参考 如何创建一个vue项目&#xff08;详细步骤&#xff09; 方法一&#xff1a;vue init webpack 项目名&#xff08;vue-cli2.x的初始化方式&#xff09; vue init webpack blog 创建项目(blog 是项目名…

Vuex:一个强大的状态管理模式

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

故障诊断实验台 | PT300mini微型振动故障实验台

很多同学因为实验数据而被困扰&#xff0c;目前数据来源有3方面&#xff0c;公开实验数据集、校企合作项目实际数据、自制实验台数据。 公开实验数据集被用烂了&#xff0c;容易被审稿人质疑&#xff1b; 校企合作项目实际数据缺少故障数据&#xff0c;数据需保密&#xff0c;…

抖音同城榜上榜策略

随着抖音的普及&#xff0c;越来越多的人开始使用抖音来展示自己的才华、记录生活或者做推广。但是&#xff0c;如何让自己的短视频在抖音同城榜上榜&#xff0c;成为本地热门话题呢&#xff1f;下面&#xff0c;我将分享一些实用的策略&#xff0c;帮助您实现这一目标。 抖音同…

.NET验收

验收通用模板&#xff1a; 1.该资料计划看几天&#xff1f; 实际看了几天&#xff1f; 计划7天&#xff0c;实际看了9天 2.多少天一篇总结&#xff1f;将总结列出来。 一周总结一篇。 博客地址:3.这个资料相较于之前资料共同的内容是什么&#xff1f; 不同的(需要强化学习)…

“数聚瑞安 · 创新未来”中国·瑞安第四届创新创业大赛角逐火热,初赛结果已公布!

“数聚瑞安 创新未来”中国瑞安第四届创新创业大赛得分排行榜上&#xff0c;各参赛队伍的分数不断被刷新。由中共瑞安市委、瑞安市人民政府主办&#xff0c;瑞安市科学技术局承办&#xff0c;华为&#xff08;浙南&#xff09;工业互联网创新中心、瑞安华数广电网络有限公司、…

分布式服务的链路跟踪 Sleuth Micrometer zipkin OpenTelemetry

由来 在分布式应用开发过程中&#xff0c;一个请求会调用多个应用&#xff0c;会有那种需要知道各个应用之间耗时的想法&#xff0c;这样可以知道一个调用的总时长以及各个组件之间的处理耗时&#xff0c;后面方便定位问题。 理论依据 起源于 google dapper 论文 https://re…

记录:移动设备软件开发(layout六大布局)

目录 前言layoutLinearLayout线性布局LinearLayout的常用属性Android&#xff1a;orientation属性Android&#xff1a;gravity属性 TableLayout表格布局TableLayout的常用属性collapsecolumns属性shrinkcolums属性stretchcoumns属性 RelativeLayout相对布局Absolute Layout绝对…

Python学习笔记——基本类型、函数、输入和输出

食用说明&#xff1a;本笔记适用于有一定编程基础的伙伴们。希望有助于各位&#xff01; 基础类型 常用的简单类型有str&#xff0c;float&#xff0c;int&#xff0c;bool等&#xff0c;常见的复杂数据类型有function&#xff0c;type&#xff0c;list&#xff0c;tuple&…