单例模式
文章目录
前言
单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。一、抛出问题
1.我们为什么要使用单例模式?
2.为什么不用静态类来代替单例模式呢?
3.多线程下如何让单例模式变得安全
这三个问题是我们在使用单例模式时最常见的问题,为什么要用?什么时候使用?为什么不能用静态类代替单例模式?他俩的区别在哪里呢?什么时候用静态类好,什么时候用单例模式好?多线程下单例模式为什么不安全,怎么样才能让他变安全?
二、回答问题
1.为什么要使用单例模式
单例模式的优点和使用场景:
- 在内存中只有一个实例对象,节省内存空间。
- 避免重复的创建和销毁对象,可以提高性能,避免对多重资源的重复占用,可以全局进行访问
- 需要频繁的实例化和销毁的对象。有状态的工具类对象,频繁访问数据库或文件对象.
根据这些不难看出,单例模式的诞生,是为了节省内存空间,避免多次重复创建和销毁对象。如果那个对象占用内存少,多次创建和销毁对于运行影响不大,但是一个很大很大的对象被重复,就会浪费内存空间,降低效率。
2.为什么不用静态类来代替单例模式
要说清楚这个问题实在很难,所以我们先搞清楚静态类和非静态的区别,然后再解释这个问题(1) 静态方法和非静态方法的区别
在C#中,使用Stack和托管堆Heap进行分配管理。而托管堆最主要的部分为GC Heap垃圾回收堆,由GC所管理,另一部分为Loader Heap加载堆,而Loader Heap最重要的信息是元数据相关信息,在堆中体现为Method Table方法表。Method Table则记录了存储的元数据信息。
请注意,所有方法都保存在Method Table中,无论是静态还是非静态,均保存在Loader Heap中。
在调用上,静态方法要比非静态方法快一点,因为非静态方法要进行实例化。
(2) 为什么要有非静态方法
那么从上面来看,非静态的速度比较慢,不如静态方法好使,为什么还要使用非静态方法呢?
答: 为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。面向对象,需要的继承和多态,都是非静态方法。
(3)什么时候使用静态类,什么时候使用单例
首先先看他们之间的优劣:
- 静态类比单例具有更好的性能,因为静态方法在编译期绑定。
- 静态方法无法被override,但非静态方法的单例模式则可以依赖继承覆盖方法。
- 如果你的需求中需要维护状态信息,则单例比静态类更适合,因为后者在维护状态信息方面是非常可怕的,并导致狡滑的 bug。
简单的说,当不需要维护任何状态,仅仅提供全局访问的方法时,使用静态类;但需要进行面向对象的开发时,使用单例。
3.多线程下如何让单例模式变得安全
(1)单线程下的单例模式
class HelloWorld
{
private static HelloWorld instance;
private HelloWorld() { }
public static HelloWorld Instance
{
get
{
if (instance == null)
instance = new HelloWorld();
return instance;
}
}
public void show()=> Console.WriteLine("单例设计模式");
}
调用:
HelloWorld helloWorld=HelloWorld.Instance;
helloWorld.show();
if (instance == null)
instance = new HelloWorld();
return instance;
这段看样子是没什么错误,但是这仅在单线程下,在多线程下,如果两个线程都访问if语句,那么肯定都是null,那么两个线程会各自创建一个对象。
(2)多线程下的改进
private static volatile HelloWorld instance=null;//不被编译器所排序
/// <summary>
/// 辅助器,本身不参与构建
/// </summary>
private static object lockHelper=new object();
private HelloWorld() { }
public static HelloWorld Instance
{
get
{
if (instance == null)
lock (lockHelper)
if (instance==null)//双检查
instance=new HelloWorld();
return instance;
}
}
极简单版:
private HelloWorld() { }
public static readonly HelloWorld Instance=new HelloWorld();//内联初始化
等同于
public static readonly HelloWorld Instance;
static HelloWorld() => Instance = new HelloWorld();
三、总结
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。