23种设计模式(五) —— 手写实现 Singleton 模式 (生成实例)

嘿,小伙伴们,今天咱们来聊聊Singleton模式,也就是单例模式。
这模式就是保证一个类只有一个实例,还提供了一个全局访问点。
下面我给大家分享三种常见的实现方法,看看哪种最适合你的项目。

首先是饿汉模式,这货在类加载时就直接初始化实例,线程安全是它的优点,但可能会造成资源浪费哦。
代码如下:
java package singleton;
public class Singleton { private static final Singleton singleton = new Singleton();
private Singleton() { System.out.println("饿汉模式:实例已创建"); }
public static Singleton getInstance() { return singleton; } }
public class Main { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println("s1 和s2 是否相同:" + (s1 == s2 )); } }
接下来是懒汉模式,它会在第一次调用时创建实例,但要注意,这货是线程不安全的哦。
代码如下:
java package singleton;
public class SingletonLazy { private static SingletonLazy singletonLazy = null;
private SingletonLazy() { System.out.println("懒汉模式:实例已创建"); }
public static SingletonLazy getInstance() { if (singletonLazy == null) { singletonLazy = new SingletonLazy(); } return singletonLazy; } }
为了解决线程安全问题,我们可以使用同步方法或者双重检查锁定。
这里我给大家展示一个双重检查锁定的例子:
java package singleton;
public class SingletonDoubleCheck { private static volatile SingletonDoubleCheck instance = null;
private SingletonDoubleCheck() {}
public static SingletonDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDoubleCheck.class) { if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } }
最后,我们来看看枚举实现。
这种方式线程安全,还能防止反射攻击,所以挺推荐的。
代码如下:
java package singleton;
public enum SingletonEnum { INSTANCE;
public void doSomething() { System.out.println("枚举单例方法调用"); } }
public class Main { public static void main(String[] args) { SingletonEnum.INSTANCE.doSomething(); } }
在实际应用中,比如Spring框架默认的单例Bean管理、系统配置、资源访问、计数器、日志记录等,都可以用到单例模式。
不过要注意线程安全、序列化、反射攻击这些问题,还有性能考虑,比如饿汉模式启动快但可能浪费资源,懒汉模式延迟加载但需要同步。

总之,这三种实现方式各有千秋,你可以根据自己的需求来选择。
我个人比较推荐枚举实现或者双重检查锁定版。
希望这篇文章能帮到你!

Singleton/单例模式

单例模式说白了就是保证一个类在整个程序中只有一个实例,而且还得提供一个全局的访问点去拿到这个实例。
为啥要有这种模式呢?因为在很多软件系统中,有些类就特别特殊,必须得保证它们只有一个实例,这样才能保证逻辑正确和效率。
这事儿得由类的设计者来搞定,而不是让使用者去操心,单例模式就是为此而生的。

实现单例模式主要有几种方式:
首先是懒汉式单例模式。
这种模式的特点是,实例是在第一次调用getInstance方法时才创建的。
在单线程环境下,这完全没问题,但在多线程环境下,可能会有多个线程同时调用getInstance,结果就创建了多个实例,这就不好了。
为了解决这个问题,可以给getInstance方法加锁,确保线程安全。
不过,每次调用getInstance都要加锁,这会导致性能开销很大。
为了进一步优化性能,可以采用双检查锁(Double-CheckedLocking)的方式,即第一次检查实例是否为空时不加锁,只有当实例为空时才加锁,并在加锁后再次检查实例是否为空。
但要注意,由于内存读写reorder的问题,双检查锁在某些情况下可能会失效。
为了防止这种情况,可以使用volatile关键字(在C++1 1 之前)或std::atomic(在C++1 1 及之后)来确保变量的可见性和顺序性。

还有一种方式是饿汉式单例模式,这种模式在类加载时就创建实例了。
这种方式是线程安全的,因为类的加载过程是由JVM(Java虚拟机)或类加载器保证线程安全的。
但是,这种方式的缺点是,即使这个实例在程序运行过程中从未被使用过,也会造成资源浪费。

对于C++1 1 及之后的跨平台实现,可以使用std::atomic和std::mutex来实现线程安全的单例模式。
这种方式结合了原子操作和互斥锁的优点,既保证了线程安全,又优化了性能。

还有一种方法是通过函数模板来实现单例模式。
这种方法在某些特定场景下可以简化代码,但需要注意其局限性和适用场景。

总的来说,实现单例模式时,有几个要点需要注意:
1 . 实例构造器可以设置为private或protected,以限制外部直接创建实例。
如果允许子类派生,则可以将构造器设置为protected。

2 . 一般不要支持拷贝构造函数和赋值运算符,因为这可能会导致多个对象实例,与单例模式的初衷违背。
可以通过将这两个函数声明为delete来明确拒绝外部调用。

3 . 在多线程环境下实现单例模式时,需要注意对双检查锁的正确实现。
可以使用volatile(在C++1 1 之前)或std::atomic(在C++1 1 及之后)来确保变量的可见性和顺序性,或者使用互斥锁来保证线程安全。

4 . 对于饿汉式单例模式,需要注意在程序结束时正确销毁实例以避免资源泄露。
对于懒汉式单例模式,可以在getInstance方法中添加适当的逻辑来确保实例在不再需要时被正确销毁(但通常单例模式的实例会在整个程序运行期间都存在)。

单例模式适用于那些在整个程序中只需要一个实例的类,比如配置管理类、日志记录类、线程池类等。
但是,需要注意的是,过度使用单例模式可能会导致代码结构不清晰、难以维护等问题。
因此,在使用单例模式时需要权衡其优缺点,并根据实际情况进行选择。

C++如何实现单例模式 C++单例模式的设计与代码示例

嘿,编程老手们!今天咱们聊聊C++里的单例模式。
这模式有点儿像编程界的VIP,不管你有多少请求,它都只会出现一个实例。
听起来简单?那就跟着我一步步来瞧瞧吧!
首先,咱们得懒散地创建这个实例,也就是懒汉式实现。
这意味着第一次调用时才会创建它,不过得手动处理好线程安全和内存问题。
我给你一个示例代码,看看怎么操作的:
cpp include include
class Singleton { private: Singleton() { std::cout << "Singleton created.\n"; } Singleton(const Singleton&)=delete; Singleton& operator=(const Singleton&)=delete; static Singleton instance; static std::mutex mutex_; public: static Singleton getInstance() { std::lock_guard lock(mutex_); if (instance == nullptr) { instance = new Singleton(); } return instance; } static void destroyInstance() { std::lock_guard lock(mutex_); if (instance != nullptr) { delete instance; instance = nullptr; } } void doSomething() { std::cout << "Singleton is doing something.\n"; } };
// 初始化静态成员 Singleton Singleton::instance = nullptr; std::mutex Singleton::mutex_;
int main() { Singleton s1 = Singleton::getInstance(); s1 ->doSomething(); Singleton s2 = Singleton::getInstance(); s2 ->doSomething(); if (s1 == s2 ) { std::cout << "Both pointers point to the same instance.\n"; } Singleton::destroyInstance(); return 0; }
关键是要记住几个要点:私有构造函数阻止外部创建实例,删除拷贝控制成员防止拷贝和赋值,静态成员变量保存唯一实例,而互斥锁保证线程安全。

然后,我们得考虑线程安全问题。
你可以用互斥锁,或者双重检查锁来优化性能,但要注意内存屏障的问题。
还有一种更简洁的方式,就是使用Meyers' Singleton模式,利用C++1 1 的特性,无需显式加锁。

还有,常见变体包括饿汉式,它一开始就创建了实例,可能有点儿浪费资源。
懒汉式(基础版)在单线程环境下是安全的,但在多线程环境下就需要小心了。

为了避免滥用,我们可以使用依赖注入或服务定位器模式,这样能降低耦合性。
当然,使用单例时也要权衡利弊,比如全局访问和资源集中管理的优点,以及耦合性增加和可测试性降低的缺点。

最后,推荐使用Meyers' Singleton,它简洁又安全。
记得在多线程环境下要考虑线程安全,避免滥用,并妥善管理资源。
希望这篇小文章能帮到你!