搜索
首页Javajava教程java中单例模式与Singleton
java中单例模式与SingletonNov 29, 2019 pm 01:46 PM
单例模式

前言

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

一、什么是单例模式

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

(推荐视频:java视频教程)  

1、单例类只能有一个实例。

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

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

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

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

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

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

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

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

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

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

二、如何创建单例模式

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

1、正常的思考逻辑顺序 

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

/// <summary>
/// 定义一个天气类
/// </summary>
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;
 }

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

 

 

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

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

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

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

     // 定义私有构造函数,使外界不能创建该类实例
     private WeatherForecast()
     {
         Date = DateTime.Now;
     }
     /// <summary>
     /// 静态方法,来返回唯一实例
     /// 如果存在,则返回
     /// </summary>
     /// <returns></returns>
     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;
    }
    /// <summary>
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// </summary>
    /// <returns></returns>
    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 是否为空,那我们再完善一下:

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

    // 定义私有构造函数,使外界不能创建该类实例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// <summary>
    /// 静态方法,来返回唯一实例
    /// 如果存在,则返回
    /// </summary>
    /// <returns></returns>
    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总是顺序的去内存里面取指令,然后将其顺序的放入指令流水线。但是指令执行时的各种条件,指令与指令之间的相互影响,可能导致顺序放入流水线的指令,最终乱序执行完成。这就是所谓的“顺序流入,乱序流出”。

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

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

例 : B b = new B();

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

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

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

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

三、Singleton = 单例 ?

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

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


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

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

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

 private readonly ILogger<WeatherForecastController> _logger;
 private readonly Feeling _feeling;

 public WeatherForecastController(ILogger<WeatherForecastController> 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++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。

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

以上是java中单例模式与Singleton的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:博客园。如有侵权,请联系admin@php.cn删除
一文理解JavaScript中的单例模式一文理解JavaScript中的单例模式Apr 25, 2023 pm 07:53 PM

JS 单例模式是一种常用的设计模式,它可以保证一个类只有一个实例。这种模式主要用于管理全局变量,避免命名冲突和重复加载,同时也可以减少内存占用,提高代码的可维护性和可扩展性。

C++ 函数重载和重写中单例模式与工厂模式的运用C++ 函数重载和重写中单例模式与工厂模式的运用Apr 19, 2024 pm 05:06 PM

单例模式:通过函数重载提供不同参数的单例实例。工厂模式:通过函数重写创建不同类型的对象,实现创建过程与具体产品类的解耦。

在PHP中,单例设计模式是什么概念?在PHP中,单例设计模式是什么概念?Aug 18, 2023 pm 02:25 PM

Singleton模式确保一个类只有一个实例,并提供了一个全局的访问点。它确保在应用程序中只有一个对象可用,并处于受控状态。Singleton模式提供了一种访问其唯一对象的方式,可以直接访问,而无需实例化类的对象。示例&lt;?php&nbsp;&nbsp;classdatabase{&nbsp;&nbsp;&nbsp;publicstatic$connection;&nbsp;&nbsp;&nbsp;privatefunc

PHP 设计模式:通往代码卓越的道路PHP 设计模式:通往代码卓越的道路Feb 21, 2024 pm 05:30 PM

导言PHP设计模式是一组经过验证的解决方案,用于解决软件开发中常见的挑战。通过遵循这些模式,开发者可以创建优雅、健壮和可维护的代码。它们帮助开发者遵循SOLID原则(单一职责、开放-封闭、Liskov替换、接口隔离和依赖反转),从而提高代码的可读性、可维护性和可扩展性。设计模式的类型有许多不同的设计模式,每种模式都有其独特的目的和优点。以下是一些最常用的php设计模式:单例模式:确保一个类只有一个实例,并提供一种全局访问此实例的方法。工厂模式:创建一个对象,而不指定其确切类。它允许开发者根据条件

PHP入门指南:单例模式PHP入门指南:单例模式May 20, 2023 am 08:13 AM

在软件开发中,常常遇到多个对象需要访问同一个资源的情况。为了避免资源冲突以及提高程序的效率,我们可以使用设计模式。其中,单例模式是一种常用的创建对象的方式,即保证一个类只有一个实例,并提供全局访问。本文将为大家介绍如何使用PHP实现单例模式,并提供一些最佳实践的建议。一、什么是单例模式单例模式是一种常用的创建对象的方式,它的特点是保证一个类只有一个实例,并提

单例模式在PHP中的常见应用场景剖析单例模式在PHP中的常见应用场景剖析Oct 15, 2023 pm 01:25 PM

单例模式在PHP中的常见应用场景剖析概述:单例模式(SingletonPattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在PHP中,使用单例模式可以有效地限制类的实例化次数和资源占用,提高代码的性能和可维护性。本文将通过分析常见的应用场景,给出具体的PHP代码示例,来说明单例模式的使用方法和好处。数据库连接管

单例模式在PHP分布式系统中的应用场景和线程安全流程单例模式在PHP分布式系统中的应用场景和线程安全流程Oct 15, 2023 pm 04:48 PM

单例模式在PHP分布式系统中的应用场景和线程安全流程引言:随着互联网的迅猛发展,分布式系统已成为现代软件开发的热门话题。而在分布式系统中,线程安全一直是一个重要的问题。在PHP开发中,单例模式是一种常用的设计模式,它可以有效地解决资源共享和线程安全的问题。本文将重点讨论单例模式在PHP分布式系统中的应用场景和线程安全流程,并提供具体的代码示例。一、单例模式的

设计模式中的单例模式与PHP中的应用设计模式中的单例模式与PHP中的应用Oct 15, 2023 am 08:02 AM

设计模式中的单例模式与PHP中的应用引言:设计模式是在软件设计过程中,经验丰富的软件工程师总结出来的一些解决特定问题的经典模式。其中,单例模式是最常用的设计模式之一。单例模式确保一个类只有一个实例,并提供了一个全局访问点来访问这个实例。在PHP中,单例模式被广泛应用于各种场景。本文将详细介绍单例模式的概念、特点以及在PHP中的具体应用,同时给出相关的代码示例

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器