欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

浅谈单例模式

程序员文章站 2022-05-18 23:26:35
单例模式 看我之前的文章,请戳: 浅谈泛型数组 来一局紧张刺激的吃鸡——浅谈装饰者模式 一起去开心的购物吧——浅谈观察者模式 记一场精彩的篮球比赛——浅谈策略模式 大家好,又见面了,今天继续我们的设计模式之旅,今天的设计模式呢,如标题所示,那就是单例模式。 在我们之前介绍的设计模式中,为了展示这个设 ......

单例模式

看我之前的文章,请戳:

浅谈泛型数组

来一局紧张刺激的吃鸡——浅谈装饰者模式

一起去开心的购物吧——浅谈观察者模式

记一场精彩的篮球比赛——浅谈策略模式

声明:本文为原创,如有转载请注明转载与原作者并提供原文链接,仅为学习交流,本人才识短浅,如有错误,敬请指正

 

大家好,又见面了,今天继续我们的设计模式之旅,今天的设计模式呢,如标题所示,那就是单例模式。

在我们之前介绍的设计模式中,为了展示这个设计模式的巨大作用,我创建了很多的类来让设计模式工作,而今天的单例模式,告诉大家一个好消息,它只有一个类,听到这里,也许有些人就开始激动了,只有一个类,那么简单的吗,快开始吧。淡定,虽然这个设计模式只有一个类,但是其实他并不是那么容易。

首先我们来看一下单例模式的定义:

单例模式确保一个类只有一个实例,并提供一个全局访问点。

在平时开发中,有的对象我们可能只需要一个,比如线程池,缓存或者日志对象,利用单例模式,我们可以确保一个类只有一个实例,而且可以通过一个方法得到这个唯一的实例。

那么如何实现呢?

众所周知,在java中我们可以通过new关键字创建一个对象,多次使用new就会创建多个对象,而反应在类中,就是我们每次都会调用类的构造方法。那么,为了让一个类只能有一个实例,如何让多次使用new就会创建多个对象这种行为不再发生呢?

首先我们来看一下经典的单例模式:

public class singleton {

private static singleton singleton;

private singleton(){}

public static singleton getinstance(){
if (singleton == null){
singleton = new singleton();
}

return singleton;
}

}

我们可以看到,singleton这个类与一般的类最大的不同,就是他的构造方法被我们声明成了一个私有方法,这意味着我们在类的外部无法再通过new操作符执行这个类的构造方法,也就是无法再实例化这个类,那我们去哪里实例化呢,没错,我们在这个类的内部实例化这个对象,可以看到,在类中的getinstance方法中,我们判断了静态变量singleton是否为null,如果为null,则调用构造方法实例化singleton这个对象,否则,直接返回sington,由于这个singleton只可能在if语句块中被赋值,这就保证了任何情况下都只会有一个singleton出现在上下文中,是吗?

我们来考虑一下,如果有多个线程在执行这段代码,会出现什么样的情形呢?

线程a执行到 if (singleton == null),此时singleton还没有实例化,于是线程a进入了singleton的实例化阶段,但是在他还没实例化对象的时候,cpu调度将线程a挂起,线程b开始执行,此时由于singleton还没有被实例化,所以if语句块线程b也进入了,然后它将singleton实例化并返回,然后线程a继续执行,从之前被挂起的地方开始执行,也实例化了一个singleton,然后返回,此时,我们就会发现在环境中出现了两个singleton实例,这是单例模式所不想看到的情况。

鉴于经典的单例模式有如此的问题,我们必须着手构建一个工作正常的单例模式,事实上,我们有几种比较简单的方式可以帮助我们实现目的。

1. 使用同步方法。

最简单的方法,我们可以在getinstance这个方法上加上关键字“synchronized”,如下:

public class singleton {

private static singleton singleton;

private singleton(){}

public static synchronized singleton getinstance(){
if (singleton == null){
singleton = new singleton();
}

return singleton;
}

}

通过添加同步,我们可以避免同时有多个个线程访问该方法,也就可以确保singleton只会被实例化一次。

2. 立即创建实例

我们发现在多线程的情形下,bug出现在我们实例化对象时,如果我们并不在我们需要时实例化而是在一开始就实例化呢?

这就引入了单例模式的两种实例化对象的方式,一种是lazy,我们可以叫他为饱汉模式,还有一直是eager,我们叫他为饿汉模式,事实上,我们的经典的单例模式的实现就是饱汉模式,他意味着,只有当我们主动调用getinstance方法时,对象才会被实例化。

对于另一种饿汉模式,代码如下:

public class singleton {

private static singleton singleton = new singleton();

private singleton(){}

public static singleton getinstance(){

return singleton;
}

}

我们可以发现,在创建对象引用时我们直接实例化了该对象,而在getinstance方法中我们直接返回了该对象,而且不需要再考虑同步,整体代码显得比饱汉模式更加简洁,那么有同学就会问了,为什么我们不默认使用饿汉模式呢,又简单又省事。

这就涉及到创建对象的成本问题,如果我们始终使用饿汉模式,那么如果要实例化对象的成本很高并且在后面的代码中又没有使用,那么这部分的性能开销就显得很多余,在大部分情况下,我们还是应该秉承着需要时再实例化的思想。