浅谈单例模式

单例模式

看我之前的文章,请戳:

浅谈泛型数组

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

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

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

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

 

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

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

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

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

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

那么如何实现呢?

众所周知,在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方法中我们直接返回了该对象,而且不需要再考虑同步,整体代码显得比饱汉模式更加简洁,那么有同学就会问了,为什么我们不默认使用饿汉模式呢,又简单又省事。

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

posted @ 2019-03-10 15:03  JRUPER  阅读(283)  评论(0编辑  收藏  举报