设计模式之单例模式
设计模式之单例模式
单例模式是设计模式中,关于生产对象类型的设计模式的一种!
单例模式的具体表现是不可以获取同一个类的多个对象,反复获取也只会得到同一个对象!
总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第1次尝试获取对象时才创建对象!
单件模式用途:
单件模式属于工厂模式的特例,只是它不需要输入参数并且始终返回同一对象的引用。
单件模式能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。它的用途十分广泛,打个比方,我们开发了一个简单的留言板,用户的每一次留言都要将留言信息写入到数据库中,最直观的方法是每次写入都建立一个数据库的链接。这是个简单的方法,在不考虑并发的时候这也是个不错的选择。但实际上,一个网站是并发的,并且有可能是存在大量并发操作的。如果我们对每次写入都创建一个数据库连接,那么很容易的系统会出现瓶颈,系统的精力将会很多的放在维护链接上而非直接查询操作上。这显然是不可取的。
如果我们能够保证系统中自始至终只有唯一一个数据库连接对象,显然我们会节省很多内存开销和 cpu 利用率。这就是单件模式的用途。
下面对单例模式的饿汉式与懒汉式进行简单介绍:
1、饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。
2、懒汉式:当程序第一次访问单件模式实例时才进行创建。
如何选择:
如果单件模式实例在系统中经常会被用到,饿汉式是一个不错的选择。
反之如果单件模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。
懒汉式在多线程中是不支持的,所以相对来说,更多的是用饿汉式。
比如,现在有 A 线程和 B 线程,A 线程刚好在这个 getInstance()方法中,刚刚判断完非空(此时为 null),即需要创建实例,然而,就是这么巧,B 线程抢到了 CPU 的执行权,A 线程 sleep 了,这时,B 线程也进行了这个判断,和 A 一样,都需要创建实例,而这时,A 也抢到了 CPU,这时,B 就 sleep 了,然后 A 执行了实例化后,B 又抢到 CPU 执行权,然后 B 也实例化,这时,出现问题了,A 和 B 都实例化了一个对象,这就是两个对象,没有单例也不唯一。
1.饿汉式单例模式
假设需要将King
类设计为单例的,首先,普通的类的代码例如:
public class King {
}
这样的类是可以随意创建对象的,例如:
King k1 = new King();
King k2 = new King();
King k3 = new King();
显然,随意创建对象是违背了单例模式的设计思想的,所以,为了避免随意对象,可以:
public class King {
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
}
当然,私有化构造方法的目的并不是“不允许创建对象”,而是“不允许随意创建对象”,为了保证在类的外部依然可以获取当前类的对象,还可以在类中添加获取对象的方法:
public class King {
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public King getInstance() {
return new King();
}
}
则外部通过调用getInstance()
方法仍可以获取对象,但是,每调用一次,都会创建一个新的对象,所以,还需要进一步调整:
public class King {
private King king = new King();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public King getInstance() {
return king; // 直接返回私有的全局属性
}
}
由于在初始化时,private King king = new King();
只会执行1次,反复调用getInstance()
方法,获取到的都是同一个对象!这就符合了单例模式的设计思想!
但是,在以上代码中,如果没有King
的对象,是无法调用getInstance()
方法,而获取对象的唯一途径就是要调用这个方法!所以,为了解决这个矛盾,需要使用static
修饰这个方法:(用类名.就可以调用方法了)
public class King {
private King king = new King();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
return king; // 直接返回私有的全局属性
}
}
一旦在getInstance()
方法的声明中添加了static
关键字,还需要在private King king = new King();
这个属性的声明中也补充static
关键字,因为“被static
修饰的成员不可以直接访问没被static
修饰的成员”,所以,代码还需要改成:
(被static
修饰的成员不可以直接访问没被static
修饰的成员:解释:static class装载的时候,就存在了。 而类的其他部分,变量,方法,都需要在类成为实例的时候才会建立。 static的生命周期长于非static的对象,所以static的成员如果访问无static的成员时。对方可能还未产生,会发生错误。所以禁止了。也就是说:静态的方法不能直接调用非静态的方法,要有对象之后才能调用非静态的方法)
-----饿汉式单例模式全代码:
//饿汉式单例模式
public class King {
private static King king = new King();
//自己创建一个类的实例化
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
return king; // 直接返回私有的全局属性
}
}
至此,单例模式就设计完成!
同时,以上单例的代码是“饿汉式单例模式”。
为什么单例对作用域是有影响的?
是因为设计为单例的对象,一定是有static修饰的,而static这个关键字,带来的编程体验是不需要new对象就可以调用它的成员,比如属性、方法,但是static成员是常驻内存的,一旦加载到内存中,只有整个程序完全结束之后,这个数据才会从内存中消失。而局部变量,只要方法调用完毕,这个变量的作用域就结束了。
所以被static修饰的成员,它的作用域是非常长的,而没有被static修饰的成员就不会一直在内存中,所以是否单例就会影响它是否在内存中存在非常长的时间,所以单例就影响到作用域。
2.懒汉式单例模式
另外,还有“懒汉式单例模式”,其特征是“不到逼不得已,不创建对象”!例如:
public class King {
private static King king;
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
return king;
}
}
但是,以上代码是存在线程安全风险的!当存在多个“同时”运行的线程时,是可能创建出多个对象的!为了解决线程安全问题,需要:
public class King {
private static King king;
private static final Object lock = new Object();
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 为了解决线程安全问题,需要锁上以下代码
synchronized(lock) {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
}
return king;
}
}
但是,一旦加了锁,效率又会变得比较低下!因为每次调用以上方法都需要锁上代码才可以继续向后执行,在多线程的应用场景中,多个“同时”运行的线程在执行这段代码是互斥的,为了解决效率偏低的问题,还会进一步改进:
-----懒汉式单例模式全代码:
//懒汉式单例模式
public class King {
private static King king;
// 自己创建一个类的实例化
private static final Object lock = new Object();
//创建一个锁对象
private King() {
// 将构造方法私有,使之不可以在类的外部来创建对象
}
public static King getInstance() {
// 判断是否有必要锁住代码
if (king == null) {
// 为了解决线程安全问题,需要锁上以下代码
synchronized(lock) {
// 判断全局的king是否被创建了对象,如果没有,则创建
if (king == null) {
king = new King();
}
}
}
return king;
}
}
至此,懒汉式的单例模式也就完成了!
总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第一次尝试获取对象时才创建对象。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库