设计模式精讲:单例模式-程序员宅基地

技术标签: java  单例模式  设计模式  

当今软件开发行业变化迅速,开发人员需要不断学习新的技术和方法,以保持竞争力。设计模式是一种被广泛使用的解决方案,它可以帮助我们解决各种软件设计问题。设计模式是一种被广泛接受的最佳实践,它们提供了一种通用的解决方案,可以在各种不同的场景中使用。本篇博客将深入讲解单例模式的概念、分类和使用场景。

单例模式顾名思义,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在应用程序中,有些对象只需要一个实例,例如线程池、缓存、日志记录器等。使用单例模式可以确保只有一个实例被创建,并提供一个全局访问点来访问该实例,从而避免了创建多个实例带来的资源浪费和不必要的复杂性。

Singleton实际上有很多种写法,但是严格来讲只有两种是完美无缺的。但实际工作中我们用的很有可能并不是完美的版本。单例模式的第一步需要我们将构造方法设为private,这就代表其他类new不出来,只能通过调用Mgr01.getInstance方法来获得已经创建好的mgr01对象。

    private static final Mgr01 INSTANCE = new Mgr01();

    private Mgr01() {
    }

    public static Mgr01 getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}

这是一个使用饿汉式实现单例模式的示例代码。类加载到内存之后只实例化一个单例,JVM保证线程安全,因为JVM保证每一个class只会load到内存一次,static变量实在class被加载到内存之后马上就进行初始化的,所以也保证只初始化一次,所以无论拿多少次,即使是多线程访问也不会出问题。

实际上要加载一个类我们也可以这么来写:Class.forName("类的名字"),只把class放到内存里而不进行实例化,如果我们用这种方式把Mgr01加到内存之后,这个static的INSTANCE是实例化的,因为他是一个静态变量,load到内存就会初始化。

但是这种写法有一个缺点,就是还没使用就初始化,可能会浪费空间。

第二种写法是采用了静态语句块,本质上和第一种方法没区别:

public class Mgr02{
    private static final Mgr02 INSTANCE;
    static {
        INSTANCE=new Mgr02();
    }
    private Mgr02() {
    }

    public static Mgr02 getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Mgr02 m1 = Mgr02.getInstance();
        Mgr02 m2 = Mgr02.getInstance();
        System.out.println(m1 == m2);
    }
}

 这两种方法的缺点就是,实例在类加载时就被创建了,如果应用程序不需要使用该实例,那么就会浪费一定的内存空间。于是有了我们的懒加载方式,俗称懒汉式:

public class Mgr03 {
    private static volatile Mgr03 INSTANCE;
    private Mgr03(){}
    public static Mgr03 getInstance()  {
        if(INSTANCE==null){
            synchronized (Mgr03.class){
                if(INSTANCE==null){
                    INSTANCE=new Mgr03();
                }
            }
        }
        return INSTANCE;
    }
    
}

懒汉式就是指,什么时候我用到什么时候我再把它初始化。这种方式在上一篇object的文章中有详细讲过它的线程安全问题以及双重验证的机制,这里就不再赘述了。

我们再来看一种静态内部类的方式(这是一种完美的写法):

public class Mgr07 {
    private Mgr07(){}
    private static class Mgr07Holder{
        private static final Mgr07 INSTANCE=new Mgr07();
    }
    public static Mgr07 getInstance(){
        return Mgr07Holder.INSTANCE;
    }
}

我们定义了一个静态内部类,在静态内部类里面初始化了Mgr07。饿汉式加载时不管用到与否,在类加载的时候就对实例进行初始化了,而Mgr07里面,如果我们只加载Mgr07的话,这里面的Mgr07Holder是不会被初始化的。一个类的静态内部类,在外面的类被加载的时候,它里面的静态的类是不会被加载的,只有当我们调用getInstance方法的时候才会被加载。因此这种方式不但实现了懒加载,还实现了只有一个实例。这种方式的线程安全是由JVM来帮我们保证线程安全的,保证每个class只被load到内存一次。

我们再来看下一种写法,JAVA的创始人之一Joshua Bloch曾经写过一本书叫做《Effective Java》,在本这书当中他提到了一种通过枚举类来实现单例模式的方法:

public enum Mgr08 {
    INSTANCE;
    public void m(){//其他业务方法
        System.out.println("m");
    }
}

在这个枚举类中只有一个取值,就是INSTANCE,当我们需要这个实例的时候直接调用Mgr08.INSTANCE就可以。这是完美中的完美:枚举单例,不仅可以解决线程同步,还可以防止反序列化。

ps:

反序列化是将序列化后的数据恢复为对象的过程。在Java中,对象可以被序列化为字节流,然后通过网络传输或者保存到文件中。当需要使用该对象时,可以将字节流反序列化为对象,从而恢复对象的状态。

反序列化可以用于实现远程方法调用(Remote Method Invocation,简称 RMI),在客户端和服务器之间传输对象。客户端可以将对象序列化为字节流,然后通过网络传输到服务器,服务器将字节流反序列化为对象,从而调用对象的方法。在这个过程中,对象的状态被保存下来,可以在客户端和服务器之间共享。

反序列化也可以用于实现对象的持久化,将对象保存到文件中,下次需要使用时再从文件中读取并反序列化为对象。这种方式可以避免对象的重新创建和初始化,提高应用程序的性能和效率。

需要注意的是,反序列化可能会带来安全问题,因为反序列化时会调用对象的构造函数和其他方法,如果不加限制地反序列化未知来源的数据,可能会导致恶意代码的执行。因此,在反序列化时需要谨慎处理,可以使用安全的序列化机制、限制反序列化的类和方法等方式来提高安全性。

Java的反射可以做到通过一个class文件,把整个class加载到内存,再new一个实例出来。前面的各种写法都可以被找到class文件通过反序列化(反射)的方式new一个实例出来,如果要考虑防止反射,最完美的写法就只有这一种了(Mgr08)。枚举单例不会被反序列化的原因是因为枚举类没有构造方法的(java语言的规定,反编译之后枚举是一个abstract class),就算拿到class文件也无法构造它的对象,它的反序列化返回的只是INSTANCE这样一个值,如果再根据这个值查询对象的话返回的时我们单例创建的同一个对象,所以严格来讲这种方法确实是最完美的方法,只是这种写法非常别扭。

实际上,在我们日常开发过程中,单例模式都是由Spring的bean工厂来保证的,不需要我们操心:

在Spring中,BeanFactory 是一个用于管理和创建 Bean 的工厂类。默认情况下,Spring 的 BeanFactory 会创建单例 Bean,也就是说,每个 Bean 只会被创建一次,并且在应用程序的整个生命周期中只存在一个实例。这种方式可以避免创建多个实例带来的资源浪费和不必要的复杂性。

Spring 的 BeanFactory 通过使用注册表(Registry)来管理 Bean 的创建和生命周期。注册表维护了一个 Bean 的缓存,当需要获取 Bean 时,会先从缓存中查找,如果缓存中存在该 Bean 的实例,就直接返回该实例,否则就创建一个新的实例,并将其加入到缓存中。在整个应用程序的生命周期中,只有一个缓存实例,所有的单例 Bean 都存储在这个缓存中。

为了保证单例模式,Spring 使用了对象池(Object Pool)技术。对象池是一种用于缓存和重用对象的技术,它可以避免创建和销毁对象带来的开销,提高应用程序的性能和效率。在 Spring 中,BeanFactory 将创建的 Bean 存储在对象池中,当需要获取 Bean 时,会先从对象池中查找,如果对象池中存在该 Bean 的实例,就直接返回该实例,否则就创建一个新的实例,并将其加入到对象池中。

需要注意的是,Spring 的单例模式是基于 BeanFactory 的,也就是说,只有在同一个 BeanFactory 中才能保证单例模式。如果使用多个 BeanFactory,那么每个 BeanFactory 都会创建一个新的实例,无法保证单例模式。因此,在实际应用中,需要根据具体的情况选择适合的 BeanFactory 实现方式。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_51264307/article/details/130218828

智能推荐

2020软考高级系统分析师,你想知道的全在这_系统分析师吧-程序员宅基地

文章浏览阅读3.9k次,点赞4次,收藏12次。2020年准备参加软考获取高级职业技术资格认证的小伙伴咱们约起吧?!去年刚参加完嵌入式系统设计师考试,并以高分通过,这里给自己点个赞!自这篇发表之后有很多致力于嵌入式开发的小伙伴加我微信,关注我的微博,也有很多因此成了好朋友,甚至是同事。自前年开始,我会在CSDN、简书、GITHUB等平台将我在学习嵌入式的道路上遇到的各种问题都写成一篇技术博客,因为这样既能给日后查找资料方便,也能进一步提高自己的文档编写能力,还能广交朋友,拓展人脉,何乐而不为呢。当然也希望大家能和我一样,把自己在学习中的问题写成博客放在_系统分析师吧

[Android Exercise]仿微信游戏界面PART.1—ConstraintLayout和RecyclerView的应用_android微信游戏界面-程序员宅基地

文章浏览阅读860次。1、分析模块将每一个部分都做成一个_android微信游戏界面

关于vue-cli3的浏览器兼容性_@vue/babel-preset-app-程序员宅基地

文章浏览阅读1.4w次,点赞3次,收藏17次。这里先给出几个链接:1.Vue-cli浏览器兼容性:https://cli.vuejs.org/guide/browser-compatibility.html#usebuiltins-usage2.browserslist:https://www.npmjs.com/package/browserslist3.babel.config.js:https://babeljs.io/..._@vue/babel-preset-app

nginx负载均衡 upstream ip_hash的用法_upstream-hash-by: "$remote_ip-程序员宅基地

文章浏览阅读7.2k次。文章目录场景参考文档用法场景负载均衡解决session共享的问题参考文档nginx.org upstream用法语法Syntax: ip_hash;Default: —Context: upstream说明Specifies that a group should use a load balancing method where requests are d..._upstream-hash-by: "$remote_ip

自适应滤波算法(LMS算法)-程序员宅基地

文章浏览阅读9.7k次,点赞6次,收藏68次。引言 LMS学习算法是由Widrow和Hoff于1960年提出的,该算法也称为Δ\DeltaΔ规则,该算法与感知器网络的学习算法在权值调整上都基于纠错学习规则,但LMS算法那更容易实现,因此得到了广泛应用。   注意:LMS算法只能训练单层网络,但这并不影响其功能,从理论上讲,多层线性网络并不比单层网络强大,它们具有同样的能力,即对于每一个多层线性网络,都具有一个等效的单层..._lms算法

【文末福利】为什么我们要掌握Linux系统编程?_学习了linux系统编程可以结合什么用-程序员宅基地

文章浏览阅读9.1k次,点赞9次,收藏14次。作为一个嵌入式开发者,我觉得基于Linux的系统编程,这个应该是绕不开的话题。本文将围绕,为何要掌握Linux系统编程这个问题,给出一些观点,希望对各位有所帮助。_学习了linux系统编程可以结合什么用

随便推点

计算机网络基础之Internet(因特网)_2.4internet的基本概念、工作方式、ip地址、万维网-程序员宅基地

文章浏览阅读1.2w次,点赞3次,收藏27次。温故:还是老规矩,学习新的知识之前我还是建议要回顾前面所学。前面我给大家讲了TCP/IP协议的知识点,尤其是在子网的划分上面我更是着重的讲了一番。所以呢,我还是希望各位朋友对这个子网划分多下一番心思,毕竟有所得也都是自己的嘛。知新:前面我已经不止一次和大家说过我后面的计划了,主要就是想和大家分享一下关于网络安全、网络应用方面的新知识。毕竟之前讲得知识点都偏向于底层,有些朋友可能理解的费劲,所以我就打算在大家学着前面的知识点的同时给大家讲讲平时能接触到的知识,这样就学的舒服些。一、Intern_2.4internet的基本概念、工作方式、ip地址、万维网

windows批处理文件-命名为1-n_for %%a in (*.jpg) do star-程序员宅基地

文章浏览阅读256次。在处理数据集的时候,将图片命名为1.jpg,2.jpg,3.jpg ......将图片放在文件夹内,在该文件夹内新建一个txt文档,重命名为***.bat(先勾选显示 文件扩展名)然后右键编辑,复制以下内容@echo offset n=0setlocal enabledelayedexpansionfor %%a in (*.jpg) do (set /a n+=1ren "%%a" "!n!.jpg")保存后关闭,运行bat文件即可。..._for %%a in (*.jpg) do star

【java】根据当前时区获取时间_java 根据asia/shanghai 换算时间简称-程序员宅基地

文章浏览阅读3.7k次。地区国家 编号 缩写 时区中国 86 CN Asia/Shanghai香港 852 HK Asia/Hong_Kong澳门 853 MO Asia/Macau台湾 886 TW Asia/Taipei新加坡 65 SG Asia/Singapore泰国 66 TH Asia/Bangkok印度 91 IN Asia/Calcutta日本 81 JP Asia/Tokyo韩国 82 KR Asia/Seoul巴基斯坦 92 PK Asia/Karachi美国 1 US America/._java 根据asia/shanghai 换算时间简称

单精度浮点数和双精度浮点数存储_双精度浮点型存储和单精度浮点型存储-程序员宅基地

文章浏览阅读694次,点赞3次,收藏2次。简要对IEEE 754中 单精度浮点数和双精度浮点数存储进行一些分析和解释,欢迎大家来补充纠正_双精度浮点型存储和单精度浮点型存储

Android屏幕适配-重点盘点_android 屏幕适配-程序员宅基地

文章浏览阅读1.1k次。享学课堂诚邀作者:周周转载请声明出处!引子屏幕适配是 android 开发/面试 绕不开的一个问题。本文 将屏幕适配的知识要点完整展现给各位读者。正文大纲android需要做屏幕适配的原因基础知识点(很重要)屏幕适配攻略正文android需要做屏幕适配的原因关键字:android碎片化android面世以来,google开源了android系统,各家厂商各自为政,导致屏幕尺寸没有统一标准,屏幕的宽高比各种各样,屏幕密度也是各个厂家攀比的资本, 导致Android开发者想要._android 屏幕适配

awvs 中文手册详细版-程序员宅基地

文章浏览阅读119次。awvs 中文手册详细版目录:0×00、什么是Acunetix Web Vulnarability Scanner ( What is AWVS?)0×01、AWVS安装过程、主要文件介绍、界面简介、主要操作区域简介(Install AWVS and GUI Description)0×02、AWVS的菜单栏、工具栏简介(AWVS menu bar & too..._awvs automatic login failed for 10.68.120.203

推荐文章

热门文章

相关标签