单例模式
为什么需要设计模式
软件开发中经常遇到需求变化的情况,为了应对这种情况,设计出更加易于维护(修改)、更灵活的代码,前人根据开发经验总结了一些准则,根据这些准则可以设计出易维护、更灵活、可复用的代码,这些准则就称为设计模式。设计模式的目的就是设计出高内聚、低耦合的代码。
单例模式
单例模式是最简单的设计模式,单例模式:确保类只有一个实例,并且类本身负责其实例化并提供一个全局访问点。
适用场景
1)有些类只能有一个实例,例如任务管理器、对话框、ID生成器、总统。
2)有些对象的创建会耗费大量资源,为了节省资源只创建一个可复用的对象,对那些需要频繁创建和销毁的对象尤其节省资源。
优点
1)由于只有一个实例,可以节省系统资源。
2)提供了对唯一实例的受控访问,即只能通过全局访问点对实例进行访问。
3)可以扩展实现可变数目的实例。
缺点
1)与单一职责有冲突(创建类实例和提供实例放在同一个方法中实现)。
2)不易扩展。单例模式没有提供抽象层,扩展很困难,只能通过直接修改代码实现扩展。
实现方式
单例模式的传统实现方式有饿汉模式和懒汉模式,这两种实现包含同样的关键点:
1)将构造器声明为私有类型,防止在类外实例化对象,只能在类内部实例化对象。
2)提供一个私有的静态成员变量。
3)提供一个公有的静态成员方法作为全局访问点。
饿汉模式
在类装载时创建单例的方法称为饿汉模式。饿汉模式的方法是在声明实例对象时直接调用构造器将其初始化,实现代码如下:
1 public class Singleton{ 2 private static Singleton instance=new Singleton(); 3 private Singleton(){}; 4 public static Singleton getInstance(){ 5 return instance; 6 } 7 }
优点
实现简单且线程安全
缺点
1)如果实例占用较多资源,过早创建实例(未使用实例)会导致资源的浪费。
2)如果实例依赖于参数或者配置文件,这种方式就不适用。
懒汉模式
单例在第一次使用时创建的方法称为懒汉模式。实现代码如下:
1 public class Singleton{ 2 private static Singleton instance; 3 private Singleton(){}; 4 public static Singleton getInstance(){ 5 if(instance==null){ 6 instance=new Singleton(); 7 } 8 return instance; 9 } 10 }
优点
1)解决了饿汉模式中无法创建依赖于参数或者配置文件的单例的情形。
2)在使用时才创建单例,避免了资源的浪费。
缺点
在多线程环境下并不合适,可能会生成多个对象实例。
懒汉模式,线程安全
通过关键字synchronized将getInstance声明为同步方法,当有线程A在访问这个方法时,其他要访问此方法的线程等待A访问完成后在访问此方法,这样就实现了线程安全的懒汉模式,实现代码如下:
1 public class Singleton{ 2 private static Singleton instance; 3 private Singleton(){}; 4 public static synchronized Singleton getInstance(){ 5 if(instance==null){ 6 instance=new Singleton(); 7 } 8 return instance; 9 } 10 }
优点
解决了懒汉模式的线程不安全问题
缺点
同步带来的是性能下降,并且懒汉模式下只需要在初次创建单例的时候同步getInstance方法,之后使用时完全不必同步。
双重检验锁
将同步的关键字synchronized改写到getInstance方法内部,如下:
1 public static Singleton getInstance() { 2 if (instance == null) { 3 synchronized (Singleton.class) { 4 instance = new Singleton(); 5 } 6 } 7 return instance ; 8 }
初始时instance为null,多个线程在“第2行"处均为真,虽然使用了同步关键字,不过仍然会串行的产生多个实例,因此需要再添加一个检测,如下:
public static Singleton getInstance() { if (instance == null) { //Single Checked synchronized (Singleton.class) { if (instance == null) { //Double Checked instance = new Singleton(); } } } return instance ; }
添加了两个检测之后,还是不能保证线程安全,因为instance=new Singleton()并不是原子操作,包含四个步骤:
1)为instance创建内存(instance为null)
2)在堆上开辟内存用于存储Singleton对象。
3)调用Singleton的构造函数初始化成员变量,形成实例
4)将instance指向Singleton实例所在(此时instance非null)
由于JVM的指令重排序,可能导致执行顺序为1-2-4-3而非1-2-3-4,则在线程A执行到4时,未执行3之前,被线程B抢占,由于此时instance!=null,所以会直接返回instance,不过此时instance指向的对象实例并未初始化,导致错误。通过使用关键字volatile修饰instance关闭指令重排序,使instance=new Singleton()按照原有顺序执行。完整代码如下:
1 public class Singleton { 2 private volatile static Singleton instance; //声明成 volatile 3 private Singleton (){} 4 public static Singleton getInstance() { 5 if (instance == null) { //first check 6 synchronized (Singleton.class) { 7 if (instance == null) { //second check 8 instance = new Singleton(); 9 } 10 } 11 } 12 return instance; 13 } 14 15 }
枚举实现
public enum Singleton{ INSTANCE; }
参考:
1)http://coolshell.cn/articles/265.html
2)http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
3)http://aleung.github.io/blog/2012/09/17/Java-volatile-/
4)http://www.importnew.com/20720.html
5)http://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html#id10