常见的设计模式:单例模式
Posted Wellhold
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见的设计模式:单例模式相关的知识,希望对你有一定的参考价值。
首先要明确一个概念,什么是设计模式?设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式往往代表要解决某方面问题的最佳实现,通常被有经验的面向对象的软件开发人员所采用。
那么什么是单例模式呢?单例模式的定义是:一个类,在全局当中,有且仅含有一个实例,并且一般是由自身去进行实例化,再向外提供一个统一的获取方法,外界只能获取到该实例,但无法对实例进行修改,仅能对其进行读取或者应用,外界在调用这个类的时候,都是调用了同一个实例,最常用的场景就是多程序读取一个配置文件时,建议配置文件封装成对象,并且使用单例模式,会方便操作其中的数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。简单说来,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都最多只存在一个(当然也可以不存在)。
基于以上描述,那么在设计单例模式的情况下,我们应该要让设计的当前类满足一下条件,才能被称为单例模式:
1.其他类和程序无法实例化当前类,所以可以知道当前类的构造函数一定是用private去进行修饰的(不可不写构造函数,因为默认的构造函数是public的)
2.当前类要可以在类中实例化唯一一个自身对象,所以这个实例可以是静态static的(根据实例化的时机不同,可以分为饿汉模式和懒汉模式,之后有说)
3.当前类要可以向外界提供一个统一的接口,提供实例,向外界提供一个getInstance方法,并且返回一个自身的唯一实例,因为其他的类无法实例化该类,所以该方法也必须是静态Static的(要么其他的类是无法获取到该类的实例的)。
基于上述思路,可以完成如下单例模式的代码:
package wellhold.bjtu.singleton; //饿汉模式 public class Single { private static Single single=new Single(); private Single() { } public static Single getInstance() { return single; } }
在代码和前文当中,提到了两个名词,那就是饿汉模式和懒汉模式,所谓的饿汉模式,就是在程序启动的时候,类在加载的时候,该单例类的实例就已经是存在的模式,即从代码当中看,在Single类在被加载的时候,就已经实例化一个实例,叫single,并且静态的存储在了内存当中,等待其他的类或程序块调用,这种模式就是饿汉模式。饿汉模式毋庸置疑是线程安全的,因为该实例是在类加载的时候就已经存在内存当中,并且其他的类仅能调用它,不能修改它。
而相对来说,懒汉模式则与饿汉模式相反,懒汉模式下,单例类在被加载的时候,实例还不存在,仅在其他对象在调用单例类提供的获取实例方式的时候,单例类才去创建这个唯一的实例(如果之前已经创建过,则不进行创建),之后再返回实例,这种模式,叫做懒汉模式。可以实现代码如下:
package wellhold.bjtu.singleton; //懒汉模式 public class Single { private static Single single; private Single() { } public static Single getInstance() { if(single==null) single=new Single(); return single; } }
可以从代码当中看出,懒汉模式这种情况下,是线程不安全的,具体可以见图(图来自:http://www.cnblogs.com/ysw-go/p/5386161.html)
两个线程,线程一和线程二同时调用了getInstance方法,当线程1执行了if判断,single为空,还没来得及执行single =new Single()创建对象,这个时候线程2就来了,它也进行if判断,single依然为空,则创建Single对象,此时,两个线程就会创建两个对象,违背我们单例模式的初衷。那么要解决这个线程安全的问题,可以使用以下三种方法:
1.加同步锁:
package wellhold.bjtu.singleton; //懒汉模式-线程安全模式一 public class Single { private static Single single; private Single() { } public synchronized static Single getInstance() { if(single==null) single=new Single(); return single; } }
但这种情况下,同步是需要开销的,而我们只需要在创建实例化的时候同步,所以又衍生出了第二种模式。
2.双重检查锁定
package wellhold.bjtu.singleton; //懒汉模式-线程安全模式二 public class Single { private static Single single; private Single() { } public static Single getInstance() { if(single==null) synchronized(Single.class) { if(single==null) single=new Single(); } return single; } }
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。
指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。
例如 instance = new Singleton() 可分解为如下伪代码:
- memory = allocate(); //1:分配对象的内存空间
- ctorInstance(memory); //2:初始化对象
- instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
- memory = allocate(); //1:分配对象的内存空间
- instance = memory; //3:设置instance指向刚分配的内存地址
- //注意,此时对象还没有被初始化!
- ctorInstance(memory); //2:初始化对象
将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在!
这个问题在 J2SE 5.0 中已经被修复,可以使用 volatile 关键字来保证多线程下的单例
//懒汉模式-线程安全模式二完整版 public class Single { private static volatile Single single; private Single() { } public static Single getInstance() { if(single==null) synchronized(Single.class) { if(single==null) single=new Single(); } return single; } }
3.静态内部类方式实现懒汉模式的单例模式
package wellhold.bjtu.singleton; //懒汉模式-线程安全模式三 public class Single { private static class LazyHolder{ private static final Single single=new Single(); } private Single() { } public static Single getInstance() { return LazyHolder.single; } }
这种模式是懒汉模式当中三中模式当中的最佳,即保证了实例化延迟,也保证了不浪费性能。
以上是关于常见的设计模式:单例模式的主要内容,如果未能解决你的问题,请参考以下文章