iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >java中单例模式与Singleton
  • 273
分享到

java中单例模式与Singleton

java教程单例模式Singleton 2021-08-10 23:08:25 273人浏览 猪猪侠
摘要

前言这一篇来源我的公众号,如果你没看过,正好直接看看,如果看过了也可以再看看,我稍微修改了一些内容,今天讲解的内容如下:一、什么是单例模式【单例模式】,英文名称:Singleton Pattern,这个模式很简单,一个类型只需要一个实例,他

前言

这一篇来源我的公众号,如果你没看过,正好直接看看,如果看过了也可以再看看,我稍微修改了一些内容,今天讲解的内容如下

一、什么是单例模式

【单例模式】,英文名称:Singleton Pattern,这个模式很简单,一个类型只需要一个实例,他是属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。

(推荐视频:java视频教程

单例类只能有一个实例。

单例类必须自己创建自己的唯一实例。

单例类必须给所有其他对象提供这一实例。

那咱们大概知道了,其实说白了,就是我们整个项目周期内,只会有一个实例,当项目停止的时候,实例销毁,当重新启动的时候,我们的实例又会产品。

上文中说到了一个名词【创建类型】的设计模式,那什么是创建类型的设计模式呢?

创建型(Creational)模式:负责对象创建,我们使用这个模式,就是为了创建我们需要的对象实例的。

那除了创建型还有其他两种类型的模式:

结构型(Structural)模式:处理类与对象间的组合

行为型(Behavioral)模式:类与对象交互中的职责分

这两种设计模式,以后会慢慢说到,这里先按下不表。

咱们就重点从0开始分析分析如何创建一个单例模式的对象实例。

二、如何创建单例模式

实现单例模式有很多方法:从“懒汉式”到“饿汉式”,最后“双检锁”模式,这里咱们就慢慢的,从一步一步的开始讲解如何创建单例。

1、正常的思考逻辑顺序

既然要创建单一的实例,那我们首先需要学会如何去创建一个实例,这个很简单,相信每个人都会创建实例,就比如说这样的:

/// 
/// 定义一个天气类
/// 
public class WeatherForecast
{
    public WeatherForecast()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}


 [HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     WeatherForecast weather = new WeatherForecast();
     return weather;
 }

我们每次访问的时候,时间都是会变化,所以我们的实例也是一直在创建,在变化:

相信每个人都能看到这个代码是什么意思,不多说,直接往下走,我们知道,单例模式的核心目的就是:

必须保证这个实例在整个系统的运行周期内是唯一的,这样可以保证中间不会出现问题。

那好,我们改进改进,不是说要唯一一个么,好说!我直接返回不就行了:

 /// 
 /// 定义一个天气类
 /// 
 public class WeatherForecast
 {
     // 定义一个静态变量来保存类的唯一实例
     private static WeatherForecast uniqueInstance;

     // 定义私有构造函数,使外界不能创建该类实例
     private WeatherForecast()
     {
         Date = DateTime.Now;
     }
     /// 
     /// 静态方法,来返回唯一实例
     /// 如果存在,则返回
     /// 
     /// 
     public static WeatherForecast GetInstance()
     {
         // 如果类的实例不存在则创建,否则直接返回
         // 其实严格意义上来说,这个不属于【单例】
         if (uniqueInstance == null)
         {
             uniqueInstance = new WeatherForecast();
         }
         return uniqueInstance;
     }
     public DateTime Date { get; set; }public int TemperatureC { get; set; }
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
     public string Summary { get; set; }
 }

然后我们修改一下调用方法,因为我们的默认构造函数已经私有化了,不允许再创建实例了,所以我们直接这么调用:

[HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     WeatherForecast weather = WeatherForecast.GetInstance();
     return weather;
 }

最后来看看效果:

这个时候,我们可以看到,时间已经不发生变化了,也就是说我们的实例是唯一的了,大功告成!是不是很开心!

但是,别着急,问题来了,我们目前是单线程的,所以只有一个,那如果多线程呢,如果多个线程同时访问,会不会也会正常呢?

这里我们做一个测试,我们在项目启动的时候,用多线程去调用:

[HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     //WeatherForecast weather = WeatherForecast.GetInstance();

     // 多线程去调用
     for (int i = 0; i < 3; i++)
     {
         var th = new Thread(
         new ParameterizedThreadStart((state) =>
         {
             WeatherForecast.GetInstance();
         })
         );
         th.Start(i);
     }
     return null;
 }

然后我们看看效果是怎样的,按照我们的思路,应该是只会走一遍构造函数,其实不是:

3个线程在第一次访问GetInstance方法时,同时判断(uniqueInstance ==null)这个条件时都返回真,然后都去创建了实例,这个肯定是不对的。那怎么办呢,只要让GetInstance方法只运行一个线程运行就好了,我们可以加一个来控制他,代码如下:

public class WeatherForecast
{
    // 定义一个静态变量来保存类的唯一实例
    private static WeatherForecast uniqueInstance;
    // 定义一个锁,防止多线程
    private static readonly object locker = new object();

    // 定义私有构造函数,使外界不能创建该类实例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// 
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// 
    /// 
    public static WeatherForecast GetInstance()
    {
        // 当第一个线程执行的时候,会对locker对象 "加锁",
        // 当其他线程执行的时候,会等待 locker 执行完解锁
        lock (locker)
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new WeatherForecast();
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string Summary { get; set; }
}

这个时候,我们再并发测试,发现已经都一样了,这样就达到了我们想要的效果,但是这样真的是最完美的么,其实不是的,因为我们加锁,只是第一次判断是否为空,如果创建好了以后,以后就不用去管这个 lock 锁了,我们只关心的是 uniqueInstance 是否为空,那我们再完善一下:

/// 
/// 定义一个天气类
/// 
public class WeatherForecast
{
    // 定义一个静态变量来保存类的唯一实例
    private static WeatherForecast uniqueInstance;
    // 定义一个锁,防止多线程
    private static readonly object locker = new object();

    // 定义私有构造函数,使外界不能创建该类实例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// 
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// 
    /// 
    public static WeatherForecast GetInstance()
    {
        // 当第一个线程执行的时候,会对locker对象 "加锁",
        // 当其他线程执行的时候,会等待 locker 执行完解锁
        if (uniqueInstance == null)
        {
            lock (locker)
            {
                // 如果类的实例不存在则创建,否则直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new WeatherForecast();
                }
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}

这样才最终的完美实现我们的单例模式!搞定。

2、幽灵事件:指令重排

当然,如果你看完了上边的那四步已经可以出师了,平时我们就是这么使用的,也是这么想的,但是真的就是万无一失么,有一个 JAVA 的朋友提出了这个问题,C# 中我没有听说过,是我孤陋寡闻了么:

单例模式的幽灵事件,时令重排会偶尔导致单例模式失效。

是不是听起来感觉很高大上,而不知所云,没关系,咱们平时用不到,但是可以了解了解:

为何要指令重排?

指令重排是指的 volatile,现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水线中,同时被执行。
指令流水线并不是串行的,并不会因为一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上。
相反,流水线是并行的,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可。比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处于“执行”阶段, 而两条加法指令在“执行”阶段就只能串行工作。
相比于串行+阻塞的方式,流水线像这样并行的工作,效率是非常高的。

然而,这样一来,乱序可能就产生了。比如一条加法指令原本出现在一条除法指令的后面,但是由于除法的执行时间很长,在它执行完之前,加法可能先执行完了。再比如两条访存指令,可能由于第二条指令命中了cache而导致它先于第一条指令完成。
一般情况下,指令乱序并不是CPU在执行指令之前刻意去调整顺序。CPU总是顺序的去内存里面取指令,然后将其顺序的放入指令流水线。但是指令执行时的各种条件,指令与指令之间的相互影响,可能导致顺序放入流水线的指令,最终乱序执行完成。这就是所谓的“顺序流入,乱序流出”。

这个是从网上摘录的,大概意思看看就行,理解双检锁失效原因有两个重点

编译器的写操作重排问题.

例 : B b = new B();

上面这一句并不是原子性的操作,一部分是new一个B对象,一部分是将new出来的对象赋值给b.

直觉来说我们可能认为是先构造对象再赋值.但是很遗憾,这个顺序并不是固定的.再编译器的重排作用下,可能会出现先赋值再构造对象的情况.

结合上下文,结合使用情景.

理解了1中的写操作重排以后,我卡住了一下.因为我真不知道这种重排到底会带来什么影响.实际上是因为我看代码看的不够仔细,没有意识到使用场景.双检锁的一种常见使用场景就是在单例模式下初始化一个单例并返回,然后调用初始化方法的方法体内使用初始化完成的单例对象.

三、Singleton = 单例 ?

上边我们说了很多,也介绍了很多单例的原理和步骤,那这里问题来了,我们在学习依赖注入的时候,用到的 Singleton 的单例注入,是不是和上边说的一回事儿呢,这里咱们直接多多线程测试一下就行:

/// 
/// 定义一个心情类
/// 
public class Feeling
{
    public Feeling()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
}


 // 单例注册到容器内
 services.AddSingleton();

这里重点表扬下评论区的@我是你帅哥 小伙伴,及时的发现了我文章的漏洞,笔芯!

紧接着我们就控制器注入服务,然后多线程测试:

 private readonly ILogger _logger;
 private readonly Feeling _feeling;

 public WeatherForecastController(ILogger logger, Feeling feeling)
 {
     _logger = logger;
     _feeling = feeling;
 }


 [HttpGet]
 public WeatherForecast Get()
 {
     // 实例化一个对象实例
     //WeatherForecast weather = WeatherForecast.GetInstance();

     // 多线程去调用
     for (int i = 0; i < 3; i++)
     {
         var th = new Thread(
         new ParameterizedThreadStart((state) =>
         {
             //WeatherForecast.GetInstance();

             // 此刻的心情
             Console.WriteLine(_feeling.Date);
         })
         );
         th.Start(i);
     }
     return null;
 }

测试的结果,情理之中,只在我们项目初始化服务的时候,进入了一次构造函数:

和我们上边说的是一样的, Singleton是一种单例,而且还是双检锁那种, 因为结论可以看出,我们使用单例模式,直接可以使用依赖注入 Sigleton 就能满足的,很方便。

四、单例模式的优缺点

【优】、单例模式的优点:

(1)、保证唯一性:防止其他对象实例化,保证实例的唯一性;

(2)、全局性:定义好数据后,可以再整个项目种的任何地方使用当前实例,以及数据;

【劣】、单例模式的缺点:

(1)、内存常驻:因为单例的生命周期最长,存在整个开发系统内,如果一直添加数据,或者是常驻的话,会造成一定的内存消耗。

以下内容来自百度百科:

优点

一、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点

一、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.net Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 c++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。

本文来自编程界网,java教程栏目,欢迎学习!

--结束END--

本文标题: java中单例模式与Singleton

本文链接: https://www.lsjlt.com/news/2748.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • javascript 单例/单体模式(Singleton)
    单例模式的三个特点: 1,该类只有一个实例 2,该类自行创建该实例(在该类内部创建自身的实例对象) 3,向整个系统公开这个实例接口 Java中大概是这个样子 复制代码 代码如下: c...
    99+
    2022-11-21
    单例 单体模式
  • .Net设计模式之单例模式(Singleton)
    一、动机(Motivation) 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器,提供一种...
    99+
    2024-04-02
  • Singleton单例模式怎么实现
    这篇“Singleton单例模式怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Si...
    99+
    2024-04-02
  • 【Java】设计模式之单例模式与工厂模式
    ✅作者简介:热爱后端语言的大学生,CSDN内容合伙人 ✨精品专栏:C++面向对象 🔥系列专栏:JavaSE精品总结 文章目录   前言1、设计模式概念及分类2、单例模式2...
    99+
    2023-10-04
    java 单例模式 设计模式
  • Java单例模式与破坏单例模式的概念是什么
    本文小编为大家详细介绍“Java单例模式与破坏单例模式的概念是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java单例模式与破坏单例模式的概念是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。什么是单例...
    99+
    2023-07-05
  • java 单例模式(懒汉式与饿汉式)
    java 单例模式单例模式是一种常用的软件设计模式。在它的可信结构中只包含一个被实例化单例的特殊类。通过单例设计模式可以把整系统中的一个类只有一个实例。单例设计模式又分为两种方式,懒汉式和饿汉式。 (1)懒汉式,就是只有当调用getInst...
    99+
    2023-05-31
    java 单例模式 ava
  • java中单例模式讲解
    目录WHATWHYHOW饿汉式实现一:静态实例参数与静态代码块实现二:静态内部类懒汉式错误一:单线程实现错误二:同步方法错误三:同步代码块之单次检查错误四:同步代码块之双重检查正确:...
    99+
    2024-04-02
  • java 中单例模式饿汉式与懒汉式的对比
    java 中单例模式饿汉式与懒汉式的对比概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点。以前我们的做法是设置一个全局变量,也就是让它使得一个对象被访问。但是它不能防止你实例多个对象。这时我们可以让类自身负责保存它的唯一实例,这个...
    99+
    2023-05-31
    java 单例模式 中单
  • Java中单例模式与多线程的示例分析
    这篇文章主要介绍了Java中单例模式与多线程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。单例模式与多线程单例模式就是全局唯一但是所有程序都可以使用的对象写单例模式...
    99+
    2023-06-20
  • Java设计模式-单例模式
    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供...
    99+
    2023-06-05
  • Java单例模式与破坏单例模式概念原理深入讲解
    目录什么是单例模式饿汉式(预加载)懒汉式(懒加载)反射破坏单例模式什么是单例模式 经典设计模式又分23种,也就是GoF 23 总体分为三大类: 创建型模式结构性模式行为型模式 Jav...
    99+
    2023-02-21
    Java单例模式 Java破坏单例模式
  • java设计模式中的单例模式简单介绍
    这篇文章主要介绍“java设计模式中的单例模式简单介绍”,在日常操作中,相信很多人在java设计模式中的单例模式简单介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java设计模式中的单例模式简单介绍”的疑...
    99+
    2023-06-02
  • Java中单例模式怎么用
    这篇文章给大家分享的是有关Java中单例模式怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。注意单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。优点在内存里只有一...
    99+
    2023-06-29
  • 分析Java中的单例模式
    本篇内容主要讲解“分析Java中的单例模式”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“分析Java中的单例模式”吧!WHAT维基百科给出了解释、实现的思路以及应该注意的地方:单例模式,也叫单子...
    99+
    2023-06-25
  • Java设计模式之单例模式
    目录什么是设计模式?单例模式是什么?单例模式设计的原则是什么?Java实现单例模式的5种方式?懒汉饿汉静态内部类双重校验锁DCL(Double Check Lock)枚举(num)总...
    99+
    2024-04-02
  • Java中的单例模式实例分析
    本篇内容介绍了“Java中的单例模式实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、定义单例模式(Singleton Patter...
    99+
    2023-06-29
  • Java设计模式之单例模式实例详解【懒汉式与饿汉式】
    本文实例讲述了Java设计模式之单例模式。分享给大家供大家参考,具体如下:单例模式就是产生一个对象实例,供外外部访问。它的应用场景就是在这个类在全局真资源需要统一访问,否则会造成混乱时,才有必要设计成单例。懒汉式,就是在使用这个对象时,才去...
    99+
    2023-05-31
    java 设计模式 单例模式
  • Java单例模式分析
    目录单例模式为什么要用单例单例的关键点几种写法懒汉式饿汉式静态内部类写法枚举单例容器实现单例参考总结单例模式 为什么要用单例 确保某个类只有一个对象,常用于访问数据库操作,服务的配置...
    99+
    2024-04-02
  • Java单例模式简单介绍
    一、概念单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,...
    99+
    2023-05-31
    java 单例模式 ava
  • java设计模式中如何实现单例模式
    这篇文章将为大家详细讲解有关java设计模式中如何实现单例模式,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。下面是一个简单的小实例://简单懒汉式 public class ...
    99+
    2023-05-30
    java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作