设计模式:单例模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

大家好!本节主要介绍设计模式中的单例模式。

简介:

单例模式,它是一种常用的软件设计模式,它属于创建类型。单例模式的主要目的是确保一个类仅有一个实例,并提供一个全局访问点。
在单例模式中,一个类只有一个实例,而且自行实例化并向整个系统提供。通常,单例模式的实现会使用一个私有静态变量来保存类的唯一实例,并提供一个公共静态方法来获取该实例。这个方法通常是懒加载的,即在第一次调用时才创建实例。

单例模式的使用场景:
1、配置文件的读取和存储:在项目中,通常需要读取和存储配置信息。使用单例模式可以避免频繁地创建和销毁配置对象,节省开销。
2、线程池的设计:在多线程应用中,线程池的设计可以使用单例模式。这样可以方便地对池中的线程进行控制。
3、日志应用:在应用程序中,日志是一个重要的部分。使用单例模式可以避免重复创建日志对象,提高性能。
4、数据库连接池:在数据库系统中,连接池的设计通常采用单例模式。这是因为数据库连接是一种珍贵的资源,重复创建和销毁连接会带来很大的性能开销。
5、网站的计数器:网站计数器是一个需要单例的场景。如果每个请求都创建一个计数器对象,会导致系统资源的浪费。使用单例模式可以节省资源。

需要注意的是,单例模式虽然有用,但也要谨慎使用,过度使用会导致代码的耦合度过高,不利于维护和扩展。

单例模式的创建步骤:
1、将构造函数的访问权限设置成private或者protected,从而不允许类的外部创建对象,保证对象数目只有一个。
2、用一个静态成员指针指向创建的对象。
3、提供了一个静态成员函数,用来获取这个对象的首地址。

单例模式的优点,主要包括:
1、保证所有对象都访问唯一实例。单例模式确保了一个类只有一个实例,避免了系统中出现多个相同实例的情况,提高了系统的可靠性。
2、减少内存开支和系统的性能开销。由于单例模式只创建一个对象实例,因此可以节省系统资源和提高系统性能。对于需要频繁创建和销毁的对象,单例模式可以提高系统的性能。
2、提供对唯一实例的受控访问。单例模式封装了它的唯一实例,可以严格控制客户怎样以及何时访问它,使得类的实例化过程可控。
4、可以扩展到多例模式。基于单例模式,可以通过单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

单例模式的缺点,主要包括:
1、系统开销。虽然单例模式可以节省内存和系统性能,但是每次引用这个类的时候都要进行实例是否存在的检查,这会增加系统的开销。
2、开发混淆。当使用一个单例模式的对象的时候,特别是定义在类库中的,开发人员必须记住不能使用new关键字来实例化对象。如果开发者看不到在类库中的源代码,他们可能会对此感到惊讶。
3、对象生命周期。单例模式没有提出对象的销毁,这可能导致内存泄漏。在提供内存管理的开发语言中,只有单例模式对象自己才能将对象实例销毁,因为只有它拥有对实例的引用。在各种开发语言中,如C++,其它类可以销毁对象实例,但这将导致单例类内部的指针指向不明。
4、扩展性不佳。单例模式是基于一种先实例化、再使用的模式,对于需要动态扩展的场景并不十分适用。
5、无法被继承。单例模式的单例对象无法被子类继承,也就无法使用子类来替换父类的实例,这在一定程度上限制了代码的灵活性和可重用性。

总之,虽然单例模式可以带来一些好处,但也不能滥用单例模式,需要考虑系统的实际需求和上下文环境,根据具体情况灵活选择是否使用单例模式。


示例:


一、C#单例模式

在C#中,我们可以使用静态构造函数和静态成员变量来实现单例模式。

public class Singleton  
{  // 静态构造函数在类被任何静态成员使用之前自动调用  static Singleton()  {  // 可以在这里进行一些初始化操作  }  // 私有静态实例变量,以便在类外部无法访问到它  private static readonly Singleton instance = new Singleton();  // 私有构造函数,以便在类外部无法访问到它  private Singleton()  {  }  // 公共静态方法,用于访问该实例  public static Singleton Instance  {  get { return instance; }  }  
}

但,这个方法存在多线程并发问题。为了解决多线程并发问题,可以使用线程安全来保证单例模式的实现。以下是一种线程安全的单例模式实现方法:

public sealed class Singleton  
{  private static readonly Lazy<Singleton> _instance = new Lazy<Singleton>(() => new Singleton());  private Singleton()  {  // Private constructor to prevent instantiation from outside the class.  }  public static Singleton Instance  {  get { return _instance.Value; }  }  
}

上述代码中,使用了Lazy<T>类型来延迟初始化单例实例,直到第一次访问Instance属性时才创建实例。由于Lazy<T>是线程安全的,因此可以在多线程环境下正确地实现单例模式。

//要使用这个Singleton实例,你可以像这样访问:
Singleton singleton = Singleton.Instance;

注意,在实现单例模式时,应该避免在构造函数中进行复杂的操作或依赖其他尚未初始化的依赖项,以避免潜在的问题。

除了上述实现方法,还可以使用Mutex、Monitor或volatile关键字等其他方式来实现线程安全的单例模式。但使用Lazy<T>是较为简洁和高效的一种实现方式。


二、java单例模式

在Java中实现单例模式有多种方法,下面给出两种常用的方法:

1、饿汉式:
这种方法是在类加载的时候就已经创建了实例,因此也称为“饿汉式”单例模式。它适用于在类加载时就需要使用到该实例的场景。

public class Singleton {  private static Singleton instance = new Singleton();  private Singleton() {}  public static Singleton getInstance() {  return instance;  }  
}

2、懒汉式:
这种方法是在需要使用实例的时候才会创建,因此也称为“懒汉式”单例模式。它适用于在类加载时不需要使用到该实例,而在运行时才需要的场景。

public class Singleton {  private static Singleton instance;  private Singleton() {}  public static synchronized Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }  
}

在上面的代码中,getInstance()方法使用了synchronized关键字来实现线程同步,以避免在多线程环境下创建多个实例。但是这种方法会造成性能上的开销,因此也可以使用双重检查锁定(Double-Checked Locking)机制来提高性能:

3、双重检查锁定

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

在上面的代码中,使用volatile关键字来保证,在instance变量被初始化为非null值之前,所有线程都看到该变量的值被初始化为null,这是JMM(Java Memory Model)的Memory OrderEffects中volatile建立happens-before关系的一个应用。这样就可以避免在第一次创建实例时进行同步,从而提高性能。


三、javascript单例模式

在JavaScript中,以下是一个基本的JavaScript单例模式的例子:

class Singleton {  constructor() {  if (typeof Singleton._instance !== 'undefined') {  throw new Error('Singleton class already has a instance!');  }  this._privateProperty = 'Hello, this is a private property!';  Singleton._instance = this;  }  get instance() {  return Singleton._instance;  }  publicMethod() {  console.log('Hello from a public method!');  }  
}  // 使用单例  
const instance1 = Singleton.instance;  
const instance2 = new Singleton(); // 实际上创建了一个新的实例,但是...  
const instance3 = Singleton.instance; // 仍然是同一个实例  console.log(instance1 === instance3); // 输出:true  
instance1.publicMethod(); // 输出:Hello from a public method!

在这个例子中,通过创建一个类,并在其构造函数中进行检查,我们确保了只有一个实例被创建。我们还定义了一个instance属性,它返回该类的唯一实例,以及一个公共方法publicMethod。

然而,这个实现方法有一个小问题:如果我们尝试通过new Singleton()来创建新的实例,虽然实际上并没有创建新的实例,但是Singleton._instance的值却被改变了。这可能导致一些意想不到的副作用。

有一种更优雅的实现单例模式的方式,那就是使用“自执行函数”:

let Singleton = (function () {  let instance;  function createInstance() {  let object = new Object("I am the instance");  return object;  }  return {  getInstance: function () {  if (!instance) {  instance = createInstance();  }  return instance;  }  };  
})();  // 使用单例  
const instance1 = Singleton.getInstance();  
const instance2 = Singleton.getInstance();  console.log(instance1 === instance2); // 输出:true

在这个版本中,我们通过创建一个自执行函数来定义了一个对象,该对象只有一个属性getInstance,该属性是一个返回实例的函数。只有当实例不存在时,getInstance才会创建新实例。因此,每次调用Singleton.getInstance()时,都会返回同一个实例。这种方式实现单例模式更为优雅,也更安全。
 

四、C++单例模式

在C++中实现单例模式有多种方式,但最常见的方式是通过在类中定义一个静态成员变量和一个静态成员函数。以下是一种常见的实现方式:

class Singleton {  
public:  static Singleton& getInstance() {  static Singleton instance;  return instance;  }  // 防止拷贝构造函数和赋值运算符被调用,从而保证单例的唯一性  Singleton(Singleton const&) = delete;  void operator=(Singleton const&) = delete;  private:  Singleton() {}  
};

这种方式的好处是简单且线程安全。在C++11及以上版本中,静态局部变量的初始化是线程安全的。因此,即使多个线程同时调用getInstance(),也只会创建一个实例。

这个实现方式还通过将构造函数私有化,防止其他地方创建新的实例。同时,将拷贝构造函数和赋值运算符设为删除状态,防止已有的实例被复制或赋值,从而保证了单例的唯一性。

如果你使用的是C++11及以上版本,可以进一步简化代码,使用std::call_once和std::promise来实现线程安全的单例模式:

#include <once_flag.h>  
#include <future_utils.h>  class Singleton {  
public:  static std::future<Singleton&> getInstance() {  std::call_once(initFlag, &Singleton::init, nullptr);  return future<Singleton&>(futureFlag.get_promise().get_future());  }  private:  static void init() {  singletonInstance = Singleton();  }  Singleton() {}  Singleton(const Singleton&) = delete;  void operator=(const Singleton&) = delete;  static std::once_flag initFlag;  static Singleton& singletonInstance;  static std::promise<Singleton&> futureFlag;  
};

这个实现方式使用了C++11中的std::call_once和std::promise,保证了在多线程环境中也只会初始化一次实例。需要注意的是,这种方式需要包含头文件<once_flag.h>和<future_utils.h>,这两个头文件在C++17及以上版本中是标准库的一部分。


五、python单例模式

在 Python 中实现单例模式有多种方式,下面介绍其中两种常见的方法。
1、使用模块变量
我们可以将单例实例作为模块变量,在模块被导入时初始化,并通过一个静态方法来获取实例。

# singleton.py  
class Singleton:  _instance = None  @staticmethod  def getInstance():  if Singleton._instance is None:  Singleton._instance = Singleton()  return Singleton._instance

使用时,我们可以从该模块中导入Singleton类,并调用getInstance()方法获取单例实例。

from singleton import Singleton  # 获取单例实例  
s1 = Singleton.getInstance()  
s2 = Singleton.getInstance()  
print(s1 is s2)  # True

2、使用元类

元类是创建类的类,可以通过定义一个元类来控制类的创建过程,确保只能创建一个实例。在 Python 中,所有类都是type类的实例,我们可以通过重写type类的__call__方法来实现单例模式。    

# singleton.py  
class SingletonType(type):  _instances = {}  def __call__(cls, *args, **kwargs):  if cls not in cls._instances:  cls._instances[cls] = super().__call__(*args, **kwargs)  return cls._instances[cls]  class Singleton:  __metaclass__ = SingletonType

使用时,我们可以直接创建Singleton类的实例,每次创建实例时都会返回同一个对象。

from singleton import Singleton  # 获取单例实例  
s1 = Singleton()  
s2 = Singleton()  
print(s1 is s2)  # True	

总的来说,使用元类的方式更加简洁和直观,但使用模块变量方式更加常见,因为它不需要重写类创建过程,同时也能保证线程安全。


六、go单例模式

在Go语言中实现单例模式可以使用以下方式之一:
1、懒汉模式:
这是最基本的单例模式实现方式,但它在多线程环境下是不安全的。

type Singleton struct {  // 单例对象的私有字段  
}  var instance *Singleton  func GetInstance() *Singleton {  if instance == nil {  instance = &Singleton{} // 创建单例对象  }  return instance  
}

在多线程环境下,如果两个线程同时调用GetInstance函数,可能会创建两个实例。

2、饿汉模式:
这种模式在程序启动时就创建了单例对象,所以称为"饿汉模式"。

type Singleton struct {  // 单例对象的私有字段  
}  var instance *Singleton = &Singleton{} // 创建单例对象  func GetInstance() *Singleton {  return instance  
}

这种模式下,单例对象在程序启动时就被创建,所以不会存在创建多个实例的情况。

3、双重检查锁定(Double-Checked Locking):
这是在懒汉模式和饿汉模式的基础上进行的改进,它通过在懒汉模式下增加了同步锁来保证在多线程环境下只创建一个实例。

type Singleton struct {  // 单例对象的私有字段  
}  var instance *Singleton  
var once sync.Once  func GetInstance() *Singleton {  once.Do(func() {  instance = &Singleton{} // 创建单例对象  })  return instance  
}

在第一次调用GetInstance时,once.Do会执行传入的函数,也就是创建单例对象。之后的调用不会再执行该函数,直接返回已创建好的实例。使用sync.Once可以保证即使在多线程环境下,也只执行一次创建实例的操作。


七、PHP单例模式

在PHP中实现单例模式有两种方式:
1、使用互斥锁(Mutex):在创建单例实例之前,使用互斥锁确保只有一个线程可以进入临界区。你可以使用PHP的扩展库提供的互斥锁函数(例如,sem_get()和sem_acquire())来同步线程。只有获得互斥锁的线程才能创建单例实例,其他线程则等待直到锁被释放。

class Singleton {  private static $instance;  private static $mutex;  private function __construct() {  // 私有构造函数  }  public static function getInstance() {  if (self::$instance === null) {  // 获取互斥锁  if (!self::$mutex) {  self::$mutex = sem_get(sem_count() + 1, 1);  sem_acquire(self::$mutex);  }  // 创建单例实例  self::$instance = new self();  // 释放互斥锁  sem_release(self::$mutex);  }  return self::$instance;  }  
}

2、使用静态初始化器(Static Initializer):在PHP 7及以上版本中,你可以使用静态初始化器来确保单例实例只被创建一次。静态初始化器是一个在类加载时执行的方法,可以在该方法中创建单例实例。

class Singleton {  private static $instance;  private function __construct() {  // 私有构造函数  }  public static function getInstance() {  if (self::$instance === null) {  self::$instance = new self();  }  return self::$instance;  }  // 静态初始化方法  public static function __clone() { }  
}

静态初始化器的__clone()方法在尝试克隆单例实例时被调用,并且什么也不做,从而阻止了通过克隆来创建新的实例。同时,使用静态初始化器时,PHP会保证类只被加载一次,因此单例实例也只会被创建一次。

这些方法可以帮助你在多线程环境下处理并发访问问题,但需要注意的是,这些方法并不能完全保证线程安全。在并发情况下,还可能存在其他并发访问问题,例如延迟初始化问题。为了确保完全线程安全,你可以考虑使用其他设计模式,如工厂模式或读写锁(ReadWrite Lock)等。
 

《完结》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/163670.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

世界国家/地区行驶方向数据

Part1数据背景 道路通行方向规则是交通规则的重要部分之一。不同国家及地区通行方向并不一样&#xff0c;受风俗、习惯、风潮因素等影响。 最近也在学道路行驶&#xff0c;结果差强人意&#xff0c;继续努力吧。祝学车的小伙伴们一次过~ Part2数据详情 今天分享的国家/地区行…

C语言——二周目——输入输出辨析

一、对输入输出的理解 1.明确输入的意义 以往的输入为默认形式&#xff08;标准输入流——stdin——键盘&#xff09;。但是输入的形式不止此一种。可以从键盘上敲出输入的数据&#xff0c;同时也可以将文件中、某个字符串甚至结构体的数据作为输入内容进行输入。 输入&#x…

Required MultipartFile parameter ‘file‘ is not present

出现这个原因我们首先想到的是加一个RequestParam("file")&#xff0c;但是还有可能的原因是因为我们的名字有错误 <span class"input-group-addon must">模板上传 </span> <input id"uploadFileUpdate" name"importFileU…

采用Spring Boot框架开发的医院预约挂号系统3e3g0+vue+java

本医院预约挂号系统有管理员&#xff0c;医生和用户。管理员功能有个人中心&#xff0c;用户管理&#xff0c;医生管理&#xff0c;科室信息管理&#xff0c;预约挂号管理&#xff0c;用户投诉管理&#xff0c;投诉处理管理&#xff0c;通知公告管理&#xff0c;科室分类管理。…

python爬虫入门详细教程-采集云南招聘网数据保存为csv文件

目录 网站地址数据提取技术介绍采集目标流程分析python代码实现教程和代码仅供学习交流&#xff0c;请勿用于其他非法用途&#xff01;欢迎加入python学习交流QQ群&#xff1a;891938703 网站地址 https://www.ynzp.com/ 这个网址特别适合新手拿来练习&#xff0c;你采集多了还…

【Java基础面试二十四】、String类有哪些方法?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;String类有哪些方法&…

10.17课上(七段显示器,递归异或与电路)

异或的递归与数电实现 用二选一选择器实现异或函数 在异或当中&#xff0c;如果有一项为0&#xff0c;就可以把那一项消掉&#xff1b;如果有一项为1&#xff0c;就是把剩下的所有项运算完的结果取反 &#xff08;由此在算法当中可以采用递归解决&#xff09; 当w1为0时&…

CleanMyMac苹果电脑清理软件是智商税吗?最全评测价格、清理效果一次说清

这是一篇CleanMyMac最全评测&#xff01;价格、清理效果一次说清&#xff0c;告诉你它真不是智商税! 升级Ventura系统之前&#xff0c;我用的是CleanMyMac X绿色版&#xff08;绝不提倡这个行为&#xff09;。更新到Ventura之后&#xff0c;之前很多绿色软件失效&#xff0c;浪…

TP5.1 导出excel文件

在 ThinkPHP 5.1 中引入 PHPExcel&#xff08;现在已被官方弃用&#xff0c;推荐使用 PhpSpreadsheet&#xff09;时&#xff0c;可以按照以下步骤进行操作&#xff1a; 在 composer.json 文件中添加 PHPExcel&#xff08;PhpSpreadsheet&#xff09;的依赖项。找到 require 部…

Pygame中实现图片的移动

在《Pygame中将鼠标形状设置为图片2-1》和《Pygame中将鼠标形状设置为图片2-2》中提到将鼠标设置为指定图片。接下来在该文章涉及到的代码基础之上&#xff0c;实现图片的移动&#xff0c;效果如图1所示。 图1 图片移动效果 从图1中可以看出&#xff0c;导入一个大猩猩的图片&…

DayDreamInGIS 逆地理编码工具(根据经纬度获取位置描述)插件源码解析

本工具调用高德地图逆地理编码api&#xff0c;根据高的地图逆地理编码api&#xff0c;实现根据经纬度获取位置描述。 总体设计逻辑&#xff0c;窗体采用WPF&#xff0c;通过属性的方式传递交互对象&#xff0c;核心处理逻辑写到button的执行逻辑中。 1.页面 页面XAML&#xf…

掌握JavaScript的练习之道:十个手写函数让你信手拈来!

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

MySQL事务MVCC详解

一、概述 MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制机制。主要是通过数据多版本来实现读-写分离&#xff0c;做到即使有读写冲突时&#xff0c;也能做到不加锁&#xff0c;非阻塞并发读&#xff0c;从而提高数据库并发性能。 MVCC只在已提交读&#xff08…

行业追踪,2023-10-18

自动复盘 2023-10-18 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Docker是一个流行的容器化平台,用于构建、部署和运行应用程序。

文章目录 Web应用程序数据库服务器微服务应用开发环境持续集成和持续部署 (CI/CD)应用程序依赖项云原生应用程序研究和教育 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专栏&#xff1a;…

mysql查看连接池的命令

查看实时连接的个数 &#xff08;瞬时值&#xff09; SHOW STATUS LIKE Threads_connected; 查看具体的链接信息 show full processlist; 数据库链接池常见的报错 Cannot create PoolableConnectionFactory (Data source rejected establishment of connection, message fr…

Android Fragment 基本概念和基本使用

Android Fragment 基本概念和基本使用 一、基本概念 Fragment&#xff0c;简称碎片&#xff0c;是Android 3.0&#xff08;API 11&#xff09;提出的&#xff0c;为了兼容低版本&#xff0c;support-v4库中也开发了一套Fragment API&#xff0c;最低兼容Android 1.6。 过去s…

Swift使用Embassy库进行数据采集:热点新闻自动生成器

概述 爬虫程序是一种可以自动从网页上抓取数据的软件。爬虫程序可以用于各种目的&#xff0c;例如搜索引擎、数据分析、内容聚合等。本文将介绍如何使用Swift语言和Embassy库编写一个简单的爬虫程序&#xff0c;该程序可以从新闻网站上采集热点信息&#xff0c;并生成一个简单…

Redis的五大基础数据类型

String 字符串类型&#xff0c;通过set关键字和get关键字来设置字符串键值对和获取字符串键值对。 hash 哈希类型&#xff0c;结构和Map<String,Map<String,stirng>>类似。 使用hset来设置哈希&#xff0c;使用hget来获取哈希&#xff0c;hget要精确到第二个key…

苍穹外卖(八) 使用WebSocket协议完成来单提醒及客户催单功能

WebSocket介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信(双向传输)——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c; 并进行双向数据传输。 HTTP协议和WebSocket协议对比&#xff1a; HTTP…