java设计模式之单例模式

  单例模式是众多设计模式的一种。单例类可以保证其类型只会生成一个实例,只拥有一个实例在很多时候是很有用的,比如说全局访问以及缓存代价高昂的资源;不过如果在多线程环境下使用单例,那就可能引入一些竞态条件问题。由于大多数编程语言并没有提供创建单例的内置机制,因此需要开发者自己来实现。

1 单例概述

  单例模式用于确保一个类只有一个实例,并且提供了实例的一个全局访问点。该模式通常与工厂模式配合使用。

  单例模式的一般应用场景:

  1)跨越整个应用程序域来访问共享数据,比如配置数据。

  2)只加载并缓存代价高昂的资源一次,这样就可以做到全局共享访问并改进性能。

  3)创建应用日志实例,因为通常情况下只需要一个即可。

  4)管理实现了工厂模式的类中的对象。

  5)延迟创建静态类,单例可以做到延迟实例化。

延伸:Spring在创建Bean时使用了单例(默认情况下,SpringBean是单例的),在JavaEE内部会使用单例,比如在服务定位器中。javaSE也在Runtime类的实现中使用了单例模式。

  不过过度的使用单例模式意味着不必要的资源缓存,无法让垃圾回收器回收对象并释放宝贵的内存资源。当然这样做也无法利用对象创建和继承的好处。大量使用单例类可能会导致内存和性能问题。并且单例类的使用对测试并不友好。


 

2 单例类的实现

2.1 单例类的简单实现

由于单例只有一个实例,因此要控制对象的创建。首先将构造方法进行私有化。接着要提供一个方法创建实例,并且保证该方法是静态的,如果实例已经存在,则返回它。

package com.hzw.singleton;

public class MySingleton{

  private static MySingleton instance;

  private MySingleton(){}

  public static MySingleton getInstance(){

    if(instance==null){

      instance = new MySingleton();

    }

    return instance;

  }

}

getInstance()方法首先会判断单例有没有创建,如果没有创建则创建它,否则,返回之前调用getInstance()方法创建的实例。后续的每次调用都是返回之前创建的MySingleton对象的实例,这段实现单例模式的代码看起来可以正常运行,不过在实际上却是存有BUG的而且是不完整的。由于对象的创建方法并不是原子的,因此在竞态条件下容易出错。在多线程环境下,会导致创建多个单例实例的后果。


 

2.2 同步单例保证线程安全或者在类加载时初始化单例类实例

要解决上面产生的竞态问题,需要在出现该问题的地方采用加锁机制。并且在实例返回后再释放锁。在java中使用synchronized关键字实现锁机制。

package com.hzw.singleton;

public class MySingleton{

  private static MySingleton instance;

  private MySingleton(){}

  public static synchronized MySingleton getInstance(){

    if(instance==null){

      instance = new MySingleton();

    }

    return instance;

  }

}

或者在加载类的同时创建单例实例,这样就不必同步单例实例的创建,并在JVM加载完所有类时就创建好单例对象(这是在类调用getInstance()方法之前发生的)。之所以可以这样,是因为静态成员与静态块是在类加载时执行的。

package com.hzw.singleton;

public class MySingleton{

  private static final MySingleton instance = new MySingleton();

  private MySingleton(){}

  public static MySingleton getInstance(){

    return instance;

  }

}

还可以使用静态块,不过这会导致延迟初始化,因为静态块是在构造方法调用之前执行的。

package com.hzw.singleton;

public class MySingleton{

  private static MySingleton instance = null;

  static{

    instance = new MySingleton();

  }

  private MySingleton(){}

  public static MySingleton getInstance(){

    return instance;

  }

}


 

2.3 使用双重检测锁

双重检测锁是一种流行的创建单例的机制,它比其他方法更加安全,因为它会在锁定单例类之前检查一次单例的创建,在对象创建前再一次检查。

package com.hzw.singleton;

public class MySingleton{

  private volatile MySingleton instance;

  private MySingleton(){}

  public static MySingleton getInstance(){

    if(instance==null){

      synchronized(MySingleton.class){

        if(instance==null){

          instance = new MySingleton();

        }

      }

    }

    return instance;

  }

}

getInstance()方法会在创建前检查私有的MySingleton实例成员两次,看它是不是null,并赋值为一个MySingleton实例。


 

2.4 使用枚举机制创建单例实例

以上方法都不是绝对安全的。比如说,开发者可以通过Java Reflection API将构造方法的访问权限改为public,这样就可以再次创建单例了。

在java中,创建单例的最佳方式是使用java5中引入的枚举类型,这也是Effective Java中极力推荐的方式。因为枚举类型本质上就是单例的,因此JVM会处理创建单例所需的大部分工作。这样通过使用枚举类型,就不需要在处理同步对象创建(单例实例的创建)和提供(获取创建的单例实例)等工作了,还能避免与初始化相关的问题。

package com.hzw.singleton;

public enum MySingletonEnum{

  INSTANCE;

  public void method1(){}

  public void method2(){}

  ....................

}

在该例中,对单例对象实例的引用是通过如下方式获得的:

MySingletonEnum mse = MySingletonEnum.INSTANCE;

一旦拥有了单例的引用,就可以调用它的方法:例如   mse.method1();

 

posted @ 2016-10-12 16:05  zwbg  阅读(238)  评论(0编辑  收藏  举报