单例模式
- 1.什么是设计模式
- 2.什么是单例模式
- 3.常见实现单例模式的两种方式
- 1.饿汉模式
- (1)特点
- (2)代码实现
- (3)线程是否安全
- 2.懒汉模式
- (1)特点
- (2)代码实现
- (3)线程是否安全
- (4)如何保证线程安全
- 解决方案:
- 进阶方案
- 3.对比懒汉模式和饿汉模式
- 1、线程安全
- 2、是否延迟加载
- 3、系统开销
- 4、不同类加载器加载的问题
1.什么是设计模式
想必大家都听说过孙子兵法吧,尤其是电视剧狂飙又带火了这本书,我们就可以将孙子兵法理解为"设计"模式,因为它里面的计谋是反复被世人使用,多数人知晓,带兵打仗的总结.
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
简单说:
模式: 在某些场景下针对某类问题的某种通用的解决方案.
- 场景:项目所在的环境
- 问题:约束条件,项目目标等
- 解决方案:通用,可复用的设计,解决约束达到目标.
—上述内容采纳于 点击此处
2.什么是单例模式
保证某个类在程序中至多存在一个实例.
在单例模式中一般只提供一个getInstance()方法来获取类的实例对象,不允许通过其他方法创建或者获取该实例对象.
其中单例模式中常见的两种设计模式是懒汉模式和饿汉模式.
3.常见实现单例模式的两种方式
1.饿汉模式
(1)特点
先通过生活中的常见举例:
这个饿汉模式的 “饿” 在这里表示急迫的意思,大家都知道,如果自己饿了之后见到食物就会狼吞虎咽,那狼吞虎咽这个过程是不是就表示很急迫.
就像咱们平时放暑假的暑假作业一样,如果是饿汉模式的话,就会迫不及待的把作业全部写完再干别的事情.
(2)代码实现
//单例模式
class Singleton{
//唯一实例的本体
private static Singleton instance = new Singleton();
public static Singleton getSingleton(){
return instance;
}
private Singleton(){};//构造方法设置为私有,这样的话就不可以从外部去new
}
- 通过private修饰Singleton属性,只可以通过
getSingleton()
方法来获取该实例. - 由于构造方法被private修饰,所以这个类是不可以被new的.
- 由于Singleton属性是被static修饰的,所以是在类加载时创建的
getSingleton()
方法也是static修饰的,属于静态方法可以通过类名.getSingleton()
调用- static修饰表示该方法/属性是通过类名调用的,与实例无关,即类加载好后就在内存中存在的,有且只有一份.
(3)线程是否安全
在类加载的时候就已经实例化了,所以该实例化没有涉及到实例化的修改操作,只是进行读取操作即多个线程读取同一个变量。在多线程情况下是线程安全的。
2.懒汉模式
(1)特点
咱们还是通过上述的暑假作业来讲解,懒汉模式,"懒"大家就可以联想到,一个懒人,非必要不写作业,说的也就是我,平时都是在暑假最后一天熬油点灯的.
那么在程序中这个 “懒” 是什么意思呢?
大家都看过小说吧,一本小说好几十万字,如果大家在看小说时,只是想看一部分,但是却把整本小说的内容都读取到了内存中,那是不是很浪费资源,并且效率很慢…所以呢,就有大佬想到了一个方法,咱们小说不是一页一页翻的嘛,他就只是提前加载出后两页的内容,当你前面的看完之后再去加载后面的内容,这样就很大程度的减少了资源的浪费,并且提高了效率.
总结:什么时候用,什么时候创建!
(2)代码实现
class SingletonLazy{
private static SingletonLazy instance;
public static SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
//构造方法设置为私有,这样的话就不可以从外部去new
private SingletonLazy(){
};
}
(3)线程是否安全
上述线程是不安全的,前面的博客写到过,多个线程修改同一个变量是会产生线程安全问题的.
(4)如何保证线程安全
我们先通过图片了解它为什么是线程不安全的
可以发现,我明明只想创建一个实例,但是却创建了两个,如果线程更多的话可能会创建更多的实例,这就涉及到了线程的安全问题,假设一个实例对象就很大,那多创建的一个甚至是多个会浪费多少资源都不得而知…
解决方案:
我们可以通过加锁的方式来保证代码的原子性,此时即使是多线程同时调用getSingleton()也不会出错~
public static SingletonLazy getInstance(){
synchronized(SingletonLazy.class)
if(instance == null){
instance = new SingletonLazy();
}
}
return instance;
}
- 优点:保证了线程安全
- 缺点:由于只需要实例一次对象,在此之后调用该方法都不会new,此时再去加锁就会浪费资源,降低效率
进阶方案
由于我们只有在第一次new的时候需要保证原子性,那么我们可以通过双重if方法来保证效率,代码如下:
public static SingletonLazy getInstance(){
if(instance == null){
synchronized (SingletonLazy.class){
if(instance == null){
instance = new SingletonLazy();
}
}
}
//除了第一次new的时候,其余都是只进行一次if就好
return instance;
}
虽然上面的if出现了两次,且内容一样,但是他们想要达到的目的是不同的.因为是在不同的时间段.
举例:我们去找实习是不是需要面试,可能此次的面试失败了,但是在后续的面试中,由于不在同一时间段,我的知识储备量大大增加,所以就有可能会面试成功
- 第一个if判断是否为空,如果不为空的话直接返回,就不再进行加锁的操作(省去了多余的加锁操作,提高了效率)
- 如果进入了第二个if的话,此时线程1创建实例是原子操作,当线程1释放锁后,线程2的
instance
已经不是null了,直接返回创建好的对象.
还有一点点的问题就是指令重排序,虽然该方法在编译器中是为了提高效率,但是也会引带出一些问题.
我们将new操作分为三个部分:
1.分配内存空间
2.实例化对象
3.给变量赋值
正常情况下如下图:
异常情况:
解决方法:
//通过volatile修饰该属性
volatile private static SingletonLazy instance;
3.对比懒汉模式和饿汉模式
1、线程安全
懒汉式需要使用同步锁来保证线程安全,而饿汉式本身就是线程安全的。
2、是否延迟加载
懒汉式是延迟加载的,而饿汉式是立即加载的。
3、系统开销
懒汉式可以避免在程序启动时就创建单例对象,从而降低系统的开销。而饿汉式会在程序启动时就创建单例对象,如果单例对象很大或需要一定时间才能创建,那么会造成一定的系统开销。
4、不同类加载器加载的问题
如果有多个类加载器,那么懒汉式会存在不同类加载器加载了不同的单例对象的情况。而饿汉式不存在这个问题。