单例模式

为什么需要设计模式

软件开发中经常遇到需求变化的情况,为了应对这种情况,设计出更加易于维护(修改)、更灵活的代码,前人根据开发经验总结了一些准则,根据这些准则可以设计出易维护、更灵活、可复用的代码,这些准则就称为设计模式。设计模式的目的就是设计出高内聚、低耦合的代码。

单例模式

单例模式是最简单的设计模式,单例模式:确保类只有一个实例,并且类本身负责其实例化并提供一个全局访问点。

适用场景

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 }
View Code

优点

实现简单且线程安全

缺点

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 }
View Code

优点

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 }
View Code

优点

解决了懒汉模式的线程不安全问题

缺点

同步带来的是性能下降,并且懒汉模式下只需要在初次创建单例的时候同步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 }
View Code

枚举实现

public enum Singleton{
   INSTANCE;
}
View Code

参考:

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

posted @ 2016-10-07 15:59  lz3018  阅读(315)  评论(0编辑  收藏  举报