单例模式的几种实现

来源:互联网 发布:淘宝电子兑换券怎么用 编辑:程序博客网 时间:2024/06/07 03:25

单例模式(Singleton Pattern)属于创建型模式,它提供了一种创建单一对象的方式。

单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。

通常,我们在new一个对象时,不能防止它被实例化多次,怎样保证它只被实例化一次呢,一个最好的办法就是让这个类自身去创建并保存它的唯一实例,再提供一个访问该实例的公共方法,那么就能保证它被实例化一次了。


说完单例模式的分类和概念,我们再说说它的具体应用。在开发和设计过程中什么时候会用到单例模式呢,什么时候我们的类只需要实例化一次呢,有些时候我们只是想节约系统资源,但有些时候我们因为功能需求只能实例化一个对象,比如:

  • 大家基本上天天都在接触的,windows的任务管理器;
  • web中的计数器,一般使用单例做缓存;
  • web中读取配置对象
  • 数据库连接池
  • 序列号生成
  • ...

关于单例模式的应用场景,具体可以参考http://www.cnblogs.com/BrainDeveloper/p/3192417.html


本文着重写单例模式的四种实现,包括懒汉式、饿汉式、静态内部类,登记式单例模式。我知道网上已经写了很多这方面的总结了,但我也想尽可能按照自己的理解来总结一下。

1、懒汉式单例模式

我们先直接来看这段代码:

package designpatterns.singleton;/** * 懒汉式单例模式 * @Description: 在被引用时才会实例化自己 * @author YanTu * @date 2017年4月25日 上午10:07:52 */public class Singleton1 {private static Singleton1 instance;//构造方法私有化,防止外界利用new创建此实例的可能private Singleton1() {}//此方法是获得本类实例的唯一全局访问点public static Singleton1 getInstance(){//若实例不存在,则new一个新实例,否则返回已有的实例if(instance == null){instance = new Singleton1();}return instance;}}

客户端测试代码:

package designpatterns.singleton;/** * 单例模式测试 * @Description: 测试是否为单例 * @author YanTu * @date 2017年4月25日 上午9:59:17 */public class SingletonTest {public static void main(String[] args) {Singleton1 s1 = Singleton1.getInstance();Singleton1 s2 = Singleton1.getInstance();if(s1 == s2){System.out.println("两个对象是相同的实例");}}}

我们可以看出单例模式除了封装它的唯一实例以外,还可以严格的控制访问,所以单例模式的最佳理解就是对唯一实例的受控访问。
以上代码我们可以看出懒汉式单例模式仅仅在客户端引用getInstance()方法时才创建实例,因此才被称之为懒汉式单例。但是,这种类型的单例模式,在多线程环境下会出现安全问题。比如多个线程同时访问Singleton1类,调用getInstance()方法,会有可能创建多个实例的。那么这时候我们就会想到在getInstance()方法上加一个同步锁,代码如下:
public static synchronized Singleton1 getInstance(){//若实例不存在,则new一个新实例,否则返回已有的实例if(instance == null){instance = new Singleton1();}return instance;}
这样就保证了在多线程环境下同时访问不会造成多个实例的生成。但是新的问题又来了,不管这个实例是否已存在,每个线程进来都要排队,这样做很大的影响性能,对于高并发的系统来说是致命的,所以还得继续优化:
package designpatterns.singleton;/** * 懒汉式单例模式 * @Description: 在被引用时才会实例化自己 * @author YanTu * @date 2017年4月25日 上午10:07:52 */public class Singleton1 {private static Singleton1 instance;//构造方法私有化,防止外界利用new创建此实例的可能private Singleton1() {}//此方法是获得本类实例的唯一全局访问点public static synchronized Singleton1 getInstance(){if(instance == null){//保证线程同步synchronized (Singleton1.class) {//若实例不存在,则new一个新实例,否则返回已有的实例if(instance == null){instance = new Singleton1();}}}return instance;}}
现在这样,我们就不用让线程每次访问都加锁,而是在实例未被创建的时候再加锁处理,同时也能保证多线程的安全,这种做法叫做双重锁定
我们需要注意的是synchronized中不能是instance,也就是说不能用对象锁,只能用类锁,因为这里instance实例还未被创建,所以不能对它加锁。
可能还有人会问为什么要对instance做两次判空,这个时候我们思考一下就知道了,如果instance为null并且同时有两个线程调用getInstance时,它们将通过第一次判断,然后由于同步锁机制,仅有一个进程进入,另一个则排队等候,而此时如果没有第二次判空操作,第一个进程创建了实例,第二个进程还是可以继续创建新的实例,这样就达不到单例的目的了。
其实除了懒汉式单例模式,另外几种单例模式的实现都不会有线程安全的问题,接下来我们先看看饿汉式的实现。

2、饿汉式单例模式
我们先来看看代码:
package designpatterns.singleton;/** * 饿汉式单例模式 * @Description: 类加载时就会实例化自己 * @author YanTu * @date 2017年4月25日 下午3:30:59 */public class Singleton2 {private static Singleton2 instance = new Singleton2();private Singleton2(){}public static Singleton2 getInstance(){return instance;}}
饿汉式单例模式,顾名思义,加载的时候就开始实例化了,事先就准备好了,什么时候要就什么时候取。因为提前实例化,也避免了懒汉式单例中所出现的线程安全问题。
不过这种实现的问题是,不管我们是否引用,内存中都会有这么一个实例,这样势必会降低内存的使用率,有没有什么方法可以避免呢,有,延迟加载,也就是下面这种实现方式。

3、静态内部类形式的单例模式
package designpatterns.singleton;/** * 静态内部类形式的单例模式 * @Description: 使用SingletonHolder静态内部类持有单例对象,则可在引用时再加载实例,以达到懒加载的目的 * @author YanTu * @date 2017年4月25日 下午4:11:49 */public class Singleton3 {private Singleton3(){}private static class SingletonHolder{private final static Singleton3 INSTANCE = new Singleton3();}public static Singleton3 getInstance(){return SingletonHolder.INSTANCE;}}
静态内部类形式的单例模式通过在内部类中定义静态属性,并在引用getInstance()方法时再进行加载并获取,以此达到了懒加载的目的,因此在内存使用率、线程安全上都有较大的优势。
以上3种实现,都存在反射和反序列化漏洞,也就是说针对上面几种单例的实现,通过反射方式仍然可以创建多个单例,而让模式失效。不过已经有同仁在研究这个漏洞了,比如:http://blog.csdn.net/hardwin/article/details/51477359

4、登记式单例模式
登记式单例实际上是对一组单例进行维护,通过map保存,在调用时先判断单例是否已经创建,是则直接返回,否则创建一个并登记到map中,并返回。这种方式有点像爬虫队列的遍历,访问过则放入已访问队列,只不过这里是map而已。spring容器对于单例Bean的管理通常会用到这种模式,这里就暂时不做补充了。看到大家关于单例模式的分享里面还有枚举形式的实现,本人暂未做研究,待以后详细研究后再做补充。

1 0