设计模式:单例模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
大家好本节主要介绍设计模式中的单例模式。
简介
单例模式它是一种常用的软件设计模式它属于创建类型。单例模式的主要目的是确保一个类仅有一个实例并提供一个全局访问点。
在单例模式中一个类只有一个实例而且自行实例化并向整个系统提供。通常单例模式的实现会使用一个私有静态变量来保存类的唯一实例并提供一个公共静态方法来获取该实例。这个方法通常是懒加载的即在第一次调用时才创建实例。
单例模式的使用场景
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这是JMMJava 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等。
《完结》