服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - 小小的单例模式竟然有这么多种写法?

小小的单例模式竟然有这么多种写法?

2021-06-11 01:18码上JavamsJava 编程技术

单例模式应该是设计模式中最容易理解也是用得最多的一种模式了,同时也是面试的时候最常被问到的模式。

小小的单例模式竟然有这么多种写法?

单例模式应该是设计模式中最容易理解也是用得最多的一种模式了,同时也是面试的时候最常被问到的模式。

1. 单例模式的定义

 

单例模式指的是一个类中在任何情况下都绝对只有一个实例,并且提供一个全局访问点。

2. 单例模式的应用场景

 

单例模式的应用非常广泛,如数据库中的连接池、J2EE中的ServletContext和ServletContextConfig、Spring框架中的ApplicationContext等等。然而在Java中,单例模式还可以保证一个JVM中只存在一个唯一的实例。

单例模式的应用场景主要有以下几个方面:

  • 当需要频繁创建一些类的时候,使用单例可以降低系统的内存压力,减少GC(垃圾回收) ;
  • 当某些类创建实例时候需要占用的资源较多,或者实例化过程耗时比较长,且经常使用的情况;
  • 当存在频繁访问数据库或者文件的对象;
  • 当对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,是不允许存在多个实例的,否则玩完;

3. 单例模式的优缺点

 

3.1 单例模式的优点
  • 单例模式可以保证内存中只有一个实例对象,从而会减少内存的开销;
  • 单例模式可以避免对资源的多重占用;
  • 单例模式设置全局访问点,可以起到优化和共享资源的访问的作用;
3.2 单例模式的缺点
  • 扩展难, 因为单例模式通常是没有接口的啊,如果想要扩展,那么你唯一途径就是修改之前的代码,所以说单例模式违背了开闭原则;
  • 调试难,因为在并发测试中,单例模式是不利于代码的调试的,单例中的代码没有执行完,也不能模拟生成一个新对象;
  • 违背单一职责原则,因为单例模式的业务代码通常写在一个类中,如果功能设计不合理,就很容易违背单一职责原则;

4. 单例模式的实现方式及其优缺点

 

4.1 单例模式的饿汉式实现

4.1.1 饿汉式标准写法

Singleton类称为单例类,通过内部初始化一次 , 隐藏构造方法, 并提供一个全局访问点的方式实现。

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式的通用写法 
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class Singleton { 
  8.     /** 
  9.      * 内部初始化一次 
  10.      */ 
  11.     private static final Singleton instance = new Singleton(); 
  12.  
  13.     /** 
  14.      * 隐藏构造方法 
  15.      */ 
  16.     private Singleton() { 
  17.     } 
  18.  
  19.     /** 
  20.      * 提供一个全局访问点 
  21.      * 
  22.      * @return Singleton 
  23.      */ 
  24.     public static Singleton getInstance() { 
  25.         return instance; 
  26.     } 
  27.  

以上饿汉式单例写法在类的初始化的时候就会进行初始化操作,并且创建对象,绝对的线程安全,因为此时线程还没有出现就已经实例化了,故不会存在访问安全的问题。

4.1.2 饿汉式静态块机制写法

饿汉式还有一种实现,那就是静态块机制,如下代码所示:

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式  饿汉式静态机制 实现 
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class HungryStaticSingleton { 
  8.      
  9.     private static final HungryStaticSingleton hungrySingleton; 
  10.     //静态代码块 类加载的时候就初始化 
  11.     static { 
  12.         hungrySingleton=new HungryStaticSingleton(); 
  13.     } 
  14.     /** 
  15.      * 私有化构造函数 
  16.      */ 
  17.     private HungryStaticSingleton(){} 
  18.  
  19.     /** 
  20.      * 提供一个全局访问点 
  21.      * @return 
  22.      */ 
  23.     public static HungryStaticSingleton getInstance() { 
  24.         return hungrySingleton; 
  25.     } 

我们分析一下这种是写法 ,可以明显的看到所以对象是类在加载的时候就进行实例化了,那么这样一来,会导致单例对象的数量不确定,从而会导致系统初始化的时候就造成大量内存浪费,况且你用不用还不一定,还一直占着空间,俗称“占着茅坑不拉屎”。

4.2 单例模式的懒汉式实现

为了解决饿汉式单例写法可能带来的内存浪费问题,这里分析一下懒汉式单例的写法。如下代码所示:

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式  懒汉式单例实现 
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class LazySimpleSingleton { 
  8.  
  9.     private static LazySimpleSingleton lazySingleton = null
  10.  
  11.     /** 
  12.      * 私有化构造函数 
  13.      */ 
  14.     private LazySimpleSingleton() { 
  15.  
  16.     } 
  17.     /** 
  18.      * 提供一个全局访问点 
  19.      * 
  20.      * @return 
  21.      */ 
  22.     public static LazySimpleSingleton getInstance() { 
  23.         if (lazySingleton == null) { 
  24.             lazySingleton = new LazySimpleSingleton(); 
  25.         } 
  26.         return lazySingleton; 
  27.     } 

这样实现的好处就是只有对象被使用的时候才会进行初始化,不会存在内存浪费的问题,但是它会在多线程环境下,存在线程安全问题。我们可以利用synchronized关键字将全局访问点方法变成一个同步方法,这样就可以解决线程安全的问题,代码如下所示:

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式  懒汉式单例实现 synchronized修饰  
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class LazySimpleSingleton { 
  8.     private static LazySimpleSingleton lazySingleton = null
  9.     /** 
  10.      * 私有化构造函数 
  11.      */ 
  12.     private LazySimpleSingleton() {} 
  13.     /** 
  14.      * 提供一个全局访问点   
  15.      * 
  16.      * @return 
  17.      */ 
  18.     public synchronized static  LazySimpleSingleton getInstance() { 
  19.         if (lazySingleton == null) { 
  20.             lazySingleton = new LazySimpleSingleton(); 
  21.         } 
  22.         return lazySingleton; 
  23.     } 

但是,这样虽然解决了线程安全的问题,可是如果在线程数量剧增的情况下,用synchronized加锁,则会导致大批线程阻塞,从而骤减系统性能。

4.3 单例模式的双重检测实现

在上述代码上进一步优化,代码如下所示:

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式  懒汉式-双重检测单例实现 
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class LazyDoubleCheckSingleton { 
  8.     // volatile 关键字修饰 
  9.     private volatile static LazyDoubleCheckSingleton lazySingleton ; 
  10.     /** 
  11.      * 私有化构造函数 
  12.      */ 
  13.     private LazyDoubleCheckSingleton() {} 
  14.     /** 
  15.      * 提供一个全局访问点 
  16.      * 
  17.      * @return 
  18.      */ 
  19.     public static LazyDoubleCheckSingleton getInstance() { 
  20.         // 这里先判断一下是否阻塞 
  21.         if (lazySingleton == null) { 
  22.             synchronized (LazyDoubleCheckSingleton.class){ 
  23.                 // 判断是否需要重新创建实例 
  24.                 if (lazySingleton == null) { 
  25.                     lazySingleton = new LazyDoubleCheckSingleton(); 
  26.                 } 
  27.             } 
  28.         } 
  29.         return lazySingleton; 
  30.     } 

()方法时,第二个线程也可以调用,但是第一个线程执行synchronized时候,第二个线程就会发现阻塞,但是此时的阻塞是getInstance()内部的阻塞。

4.4 单例模式的静态内部类实现

虽然双重检测锁的单例模式解决了线程安全和性能问题,但是毕竟涉及加锁的操作,多多少少就会到了性能的影响,下面我们分享一下更加优雅的单例模式实现,如下代码所示:

  1. /** 
  2.  * msJava 
  3.  * 
  4.  * @Description 单例模式  静态内部类单例实现 
  5.  * @Date 2021-01-23 
  6.  */ 
  7. public class LazyStaticInnerClassSingleton { 
  8.     //  在构造方法里面抛出异常真的合适? 
  9.   private LazyStaticInnerClassSingleton(){ 
  10.     if(LazyHolder.INSTANCE != null){ 
  11.         throw new RuntimeException("不允许创建多个实例"); 
  12.     } 
  13.   } 
  14.   // static 保证这个方法不会被重写 覆盖 
  15.   private static LazyStaticInnerClassSingleton getInstance(){ 
  16.       return LazyHolder.INSTANCE; 
  17.   } 
  18.   // Java 默认不会加载内部类 
  19.   private static class LazyHolder{ 
  20.       private static final LazyStaticInnerClassSingleton INSTANCE=new LazyStaticInnerClassSingleton(); 
  21.   } 
5. 总结

单例模式面试几乎必备!

原文链接:https://mp.weixin.qq.com/s/bswiOmlCV7vHdz_UhtL70Q

延伸 · 阅读

精彩推荐
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5232021-06-21
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5972021-03-08
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20