源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns
这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23种设计的模式的各种实现方式以及应用场景,非常适用于学习以及提高我们的设计思维,如果对大家有所帮助,请记得star一下给予作者一定的精神支持,你的star是我写出更好的博客的动力,谢谢大家。

目录

  • 设计模式简介
  • 单例模式的简介
  • 单例模式的实现
    • 实现方式一:饿汉式(单例对象立即加载)
    • 实现方式二:懒汉式(单例对象延迟加载)
    • 实现方式三:双重检测锁实现(不建议使用)
    • 实现方式四:静态内部类实现方式(懒加载方式)
    • 实现方式五:枚举
  • 反射破解单例模式
  • 枚举单例模式的解释
  • 单例模式总结

设计模式简介

将设计者的思维融入大家的学习和工作中,更高层次的思考!
• 创建型模式:
– 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

• 结构型模式:
– 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

• 行为型模式:
– 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

本次文章介绍的是单例模式的五种实现方式

单例模式的简介

• 核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

• 常见应用场景:
– Windows的Task Manager(任务管理器)就是很典型的单例模式
– windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
– 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
– Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
– 在servlet编程中,每个Servlet也是单例
– 在spring MVC框架/struts1框架中,控制器对象也是单例

• 单例模式的优点:
– 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,
则可 以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决

– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

• 常见的五种单例模式实现方式:
– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)

– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)

单例模式的实现

实现方式一:饿汉式(单例对象立即加载)

• 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
• 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

需要注意的三点:
第一点:需要将构造器私有,构造器私有之后别人就访问不了了,只有自己可以用;
第二点:提供一个属性,这个属性是static变量,并且是私有的,static变量是类变量,从属于这个类,那么这个类就只有这么一个属性,它就指定了这个对象;
第三点:提供一个开放的方法,别人只能从这里取对象。
以上三点的描述,详细代码见以下的第一个!!

/*** 测试饿汉式单例模式*  --所谓饿汉式,表示的是很饿,上来就把你吃了,相当于当类加载器加载以下类的时候,*    加载的时候就把这个对象New出来,初始以下的静态属性,不管你要不要,上来就给建好。*    这个就是立即加载。这个就是不好的地方。**  --特点:*  1.线程安全:方法前不需要加synchronized,因为我们去创建对象时,在类初始化时立刻加载,*             在类加载器加载对象时是一个天然的线程安全模式。*             不存在创建对象/初始化属性多线程不安全的问题,所以天然线程安全。**  2.显然不加同步标记的话,效率就高。*/
public class Demo01 {//类初始化时,立即加载这个对象(没有延时加载的优势),加载类时,天然的是线程安全的private final static Demo01 INSTANCE = new Demo01();//提供私有的构造器,一旦构造器私有,别人就无法去new这个对象了,保证了内存中只有这么一个对象private Demo01(){}//方法没有同步,调用效率高public static Demo01 getInstance(){return INSTANCE;}
}

实现方式二:懒汉式(单例对象延迟加载)

• 要点:
– lazy load! 延迟加载, 懒加载! 真正用的时候才加载!

• 问题:
– 资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
–调用的效率低。

注意:如果类创建对象时用到的代价很高,那么就使用延时加载,也就是懒汉式;如果类调用效率非常频繁,就用饿汉式。

代码为:

/*** 懒汉式单例模式*/
public class Demo02 {//类初始化时,不初始化这个对象(实现了延时加载,真正用到的时候才去创建)private static Demo02 INSTANCE;//构造器私有,一旦构造器私有,别人就无法去new这个对象了,保证了内存中只有这么一个对象private Demo02(){}//方法同步,调用效率低public static synchronized Demo02 getInstance(){if(INSTANCE==null){INSTANCE = new Demo02();}return INSTANCE;}}

实现方式三:双重检测锁实现(不建议使用)

• 这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,
只有第一次才同步创建了以后就没必要了。
• 问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
代码如下所示:

/*** 双重检测锁模式的懒汉式,DCL懒汉式*/
public class Demo03 {private static volatile Demo03 INSTANCE;private Demo03(){}public static Demo03 getInstance(){//先判断对象是否已经实例化过,没有实例化才能进入加锁代码if(INSTANCE==null){//对类对象加锁synchronized (Demo03.class){if(INSTANCE==null){INSTANCE = new Demo03();}}}return INSTANCE;}
}
/*** 为什么要加 volatile关键字修饰 instance* 我们创建一个对象一般的步骤就是:*  1、分配内存空间*  2、执行构造方法,初始化对象*  3、把这个对象指向对应的内存空间*  一般情况下,我们期望的是执行123这样的顺序,但是真实可能执行132,比如这个时候A线程是这样的方式*  先分配内存空间,然后在把这个内存空间占用,再把这个对象放进去,在多线程情况下,如果这个时候来了个线程B* 线程B过来以后,由于是先去指向对应的内存空间,它会认为 instance 不等于null,它就会直接return回去,* 此时的这个instance还没有完成构造,这个时候空间是一片虚无,就会出问题,因此加一个volatile关键字* 可以避免指令重排,保证可见性。*
**/

实现方式四:静态内部类实现方式(懒加载方式)

基本思路:首先是静态内部类,在里面定义单例对象,然后也提供一个方法getInstance(),通过调用静态内部的方法进行访问,构造器私有,这种方式不仅线程安全,还是懒加载模式。

当我们第一次去初始化这个类的时候,并不会立即初始化它的静态内部类,当真正要用的时候,才会通过getInstance()这个方法去调用InnerClass.INSTANCE ;从而去加载内部类中的代码,使用时也不存在同步的问题。调用效率也不错,很多开源的都用这个。

• 要点
– 外部类没有static属性,则不会像饿汉式那样立即加载对象。
– 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。 INSTANCE 是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
– 兼备了并发高效调用和延迟加载的优势!

优点
1、线程安全,还是懒加载模式
2、调用效率高
3、实现了延时加载
代码实现:

/*** 静态内部类实现单例模式** 优点:* 1、线程安全,还是懒加载模式* 2、调用效率高* 3、实现了延时加载*/
public class Demo04 {//在静态内部类中定义单例对象,因为第一次初始化这个类时并不会立即初始化静态内部类private static class InnerClass{private static final Demo04 INSTANCE = new Demo04();}//构造器私有private Demo04(){}//通过静态内部类获取实例public static Demo04 getInstance(){return InnerClass.INSTANCE; //}
}

实现方式五:枚举

• 优点:
– 实现简单
– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

• 缺点:
– 无延迟加载

代码如下:

/*** 使用枚举方式实现单例模式• 优点:– 实现简单– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!*/
public enum Demo05 {/***  定义一个枚举元素,它就代表了Singleton的一个实例*/INSTANCE;public Demo05 getInstance(){return INSTANCE;}}

反射破解单例模式

以上五种单例模式的实现方式中,前四种方式都是不太安全的,饿汉、懒汉、双重检查锁、静态内部类这四种方式都可以使用反射进行破解。

下面以破解饿汉式为例:

//测试使用反射破坏单例(懒汉为例)public static void main(String[] args) throws Exception{Demo01 instance = Demo01.getInstance();//获取通过反射私有构造器(这里null表示获取无参)Constructor<Demo01> declaredConstructor = Demo01.class.getDeclaredConstructor(null);//破坏私有构造器declaredConstructor.setAccessible(true);//通过反射创建对象Demo01 instance2 = declaredConstructor.newInstance();//通过输出可以明显的看出创建了两个对象,单例模式被破解了System.out.println(instance);System.out.println(instance2);}


可以发现,已经创建了新的对象,按照单例模式的说法,它们应该是一个对象才对,但是结果证明,通过反射能够破解掉单例模式,通过反射创建了两个不同的对象,其他的你们也可以使用这种方式去测试一下,结果都是一样的。

那么我们要怎么解决反射破解单例模式这样的一个问题呢?首先我们可以去查看反射的源码:

源码中我们可以看见这么一句话,如果你的这个类型是枚举类型,想要通过反射去创建对象时会抛出一个异常(不能通过反射创建枚举对象);

枚举单例模式的解释

枚举是JDK1.5出来的,自带单例模式。
那么我们来看一下枚举怎么实现单例模式:

/*** 枚举实现单例*/
public enum  EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}
}class Test{public static void main(String[] args) {EnumSingle instance1 = EnumSingle.INSTANCE;EnumSingle instance2 = EnumSingle.INSTANCE;System.out.println(instance1);System.out.println(instance2);}
}

输出:

接下来我们测试一下,反射能不能破坏枚举实现的单例:
这里我用原来的代码的进行测试,就是名字不一样而已,将原来的Demo05改为了EnumeSingle。

首先我们查看一下源码中是有参构造还是无参构造,我们一个一个的侧,找到EnumeSingle的class文件,查看里面是无参构造,然后我们使用反射去将私有的构造器的私有属性破除掉。

我们发现class文件里面确实有私有的无参构造器,下面我们去测试一下看能不能通过构造器破解掉枚举的单例模式:

//测试通过反射是否能够破解枚举方式的单例模式
class Test{public static void main(String[] args) throws Exception{EnumSingle instance1 = EnumSingle.INSTANCE;//使用反射获取无参构造器Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);//将构造器的私有属性破除掉declaredConstructor.setAccessible(true);//获取对象EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

输出:

然后竟然抛出了没有无参构造这么一个异常,它说我们这个类里面没有一个空参的构造器,前面给图中不就有一个无参构造器吗?这难道不是坑爹吗?而反射的newInstance源码中。正常的报错应该是以下错误(不能使用反射创建枚举对象):

那就说明IDEA骗了我们,IDEA告诉我们EnumeSingle.class文件中有一个无参构造方法,但是通过Java程序运行我们知道这里面根本没有这玩意儿,接下来我们去分析一下是什么原因。

我们使用反编译工具去编译一下,将EnumeSingle.class文件反编译成java文件。

反编译出来的Java文件,查看其中的代码发现IDEA是个骗子,这里用了有参构造,
这个时候我们就去修改代码继续测试:

/*** 枚举实现单例*/
public enum  EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}
}class Test{public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {EnumSingle instance1 = EnumSingle.INSTANCE;//使用反射获取无参构造器Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//将构造器的私有属性破除掉declaredConstructor.setAccessible(true);//获取对象EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

这个时候输出:

结果还是证明了反射不能破解枚举实现的单例模式,果然如反射的源码中所述不能破解枚举,枚举还是牛逼啊。

单例模式总结

• 常见的五种单例模式实现方式
– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)

• 如何选用?
– 单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式

– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式

寄言:
学习设计模式时一定要动手去敲代码,再加以理解,尤其是饿汉式和懒汉式,一定要熟悉到自己能够手写出来,因为面试的时候经常会让你手写出来(亲测)。

源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns
这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23种设计的模式的各种实现方式以及应用场景,非常适用于学习以及提高我们的设计思维,如果对大家有所帮助,请记得star一下给予作者一定的精神支持,你的star是我写出更好的博客的动力,谢谢大家。

Java23种设计模式之单例模式的五种实现方式、反射破解单例模式、不能破解枚举单例模式详解相关推荐

  1. Java 单例模式常见五种实现方式

    定义 单例模式,属于创建类型的一种常用的软件设计模式. 单例模式最初的定义出现于<设计模式>(艾迪生维斯理, 1994):"保证一个类仅有一个实例,并提供一个访问它的全局访问点. ...

  2. 五种IO模型:操作系统五种IO模型大全

    文章目录 五种IO模型:操作系统五种IO模型大全 一.IO模型简介 1.1 操作系统的内存简介 1.1.1 操作系统的应用与内核 1.1.2 内核空间与用户空间 1.1.3 CPU指令等级 1.1.4 ...

  3. Java 枚举(1): 详解7种常见的用法

    目录 用法一:常量 用法二:switch 用法三:向枚举中添加新方法 用法四:覆盖枚举的方法 用法五:实现接口 用法六:使用接口组织枚举 用法七:关于枚举集合的使用 JDK1.5引入了新的类型--枚举 ...

  4. [Python图像识别] 五十.Keras构建AlexNet和CNN实现自定义数据集分类详解

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  5. 计算机网络工程师题库华为,近五年华为各类工程师面试精典题库及答案详解.pdf...

    近五年华为各类工程师面试精典题库及答案详解 近五年华为各类工程师面试精典题 近五年华为各类工程师面试精典题 近近五五年年华华为为各各类类工工程程师师面面试试精精典典题题 库及答案详解 库及答案详解 库 ...

  6. osgEarth的Rex引擎原理分析(三十五)osgEarth地球椭球体ellipsoid 大地基准面datum 地图投影Projection详解

    目标:(二十九)中的问题83 地球椭球体的中心为地心,形状为椭球体 大地基准面是适应某一区域的椭球体,球体中心不一定在地心 地图投影是球面和平面映射关系的方法 Horizontal Datum A d ...

  7. java设计模式(一)——五种创建型设计模式

    一.什么是设计模式? 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. ...

  8. 单例对象会被jvm的gc时回收吗_设计模式专题02-单例五种创建方式

    单例五种创建方式(下一篇:工厂模式) 什么是单例 保证一个类只有一个实例,并且提供一个访问该全局访问点 单例应用场景 1. Windows的Task Manager(任务管理器)就是很典型的单例模式( ...

  9. 单例模式的五种实现形式(懒汉式,饿汉式,双空判断,内部类,枚举)

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创 ...

最新文章

  1. JQuery -- this 和 $(this) 的区别
  2. pfSense 2.4.4-RELEASE现已发布!
  3. buu rsarsa
  4. P2521 [HAOI2011]防线修建
  5. this指向undefined uiapp_this为什么会为undefined?
  6. Sql Server之旅——第十站 简单说说sqlserver的执行计划
  7. JSONArray.fromObject不执行且不报错问题的解决
  8. 安卓10不支持qmc解码_官宣:安卓10已发布!21款手机已适配,小米华为率先支持...
  9. 关于QT中奇数个汉字出现newline in constant的错误
  10. @enableautoconfiguration注解作用_如何让代码变“高级”-Spring组合注解提升代码维度(这么有趣)...
  11. java之SpringMVC配置!配置!配置!
  12. AMD,CMD,UMD,CommonJS
  13. CentOS7|Redhat7挂载NTFS格式磁盘
  14. grid 与axis
  15. 关于自编码器的核心点理解
  16. (71)--爬取拉勾网招聘信息
  17. vue 使用高德地图 api
  18. chipseq MACS2 call peaks 报错解决方法——创建虚拟环境
  19. 如何进行旅游app开发定制
  20. 拱火AI大战全球最强法务部,艺术家为抵制AI画画出新招

热门文章

  1. bugkuweb题-ctf入门(18-34)
  2. DAU 统计,日活跃用户数 (DAU) 是衡量一个产品表现的重要指标。 需要编写程序,根据给定的某一天的 N 条访问记录,对当天的用户总数 M 进行统计。
  3. java uuid 32_Java生成32位UUID
  4. 耐候玻璃水泥的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  5. python类的使用-汽车租赁系统
  6. 2019/09/17 03-杨辉三角对称解法和单行列表解法
  7. Linux常用命令——unzip命令
  8. docker构建jre镜像
  9. Javascript 改变CSS样式
  10. Teradata给企业一双“慧眼”