Singleton单例模式

     在最初学习设计模式的时候,课堂上钟老师介绍了Singleton模式的饿汉式和懒汉式的简单实现,具体如下:

饿汉式单例模式

     特点:对象预先加载,在类创建好的同时对象实例生成。getInstance()方法调用反应速度快,代码简练。同时是ThreadSafe的。

     缺点:每次程序运行不管有没有用到该实例,都会进行对象实例的初始化,多占用了内存。

   
   
   
   
  1. /**

  2. * Hunger Type Singleton

  3. *  

  4. * @ThreadSafe

  5. *

  6. */

  7. class Singleton {  

  8.    private static final Singleton instance = new Singleton();  

  9.    private Singleton() { }  

  10.    public static Singleton getInstance() {

  11.        return instance;  

  12.    }  


  13.    // Other functions

  14. }

懒汉式单例模式

     特点:在需要实例时才进行对象创建,相比饿汉式单例来说更节省内存。

     缺点:线程不安全,需要进行同步操作。第一次调用getInstance()时反应不快。

  
  
  
  
  1. /**

  2. * Lazy Type Singleton

  3. *

  4. * @NoThreadSafe

  5. *

  6. */

  7. class Singleton {

  8.    private static Singleton instance = null;

  9.    public static Singleton getInstance() {  

  10.        if (instance == null) {

  11.            instance = new Singleton();

  12.        }

  13.         return instance;

  14.     }


  15.    // Other functions

  16. }  


     而要想在多线程环境下安全地使用懒汉式则要使用synchronized来进行同步:

   
   
   
   
  1. /**

  2. * Thread Safe Lazy Type Singleton

  3. *

  4. * @ThreadSafe

  5. *

  6. */

  7. class Singleton {

  8.    private static Singleton instance = null;

  9.    public static synchronized Singleton getInstance() {  

  10.        if (instance == null) {

  11.            instance = new Singleton();

  12.        }

  13.        return instance;

  14.    }


  15.    // Other functions

  16. }

     上面的代码使用了同步方法来保证线程安全,但效率上要差一些。应该尽量避免使用synchronized来标记方法。转而使用同步方法块来进行同步,还要尽可能使同步方法中的代码尽量少,不将某些执行时间长、甚至可能阻塞的操作(如I/O等待)放到同步方法块中。

     更改后的代码如下:

  
  
  
  
  1. /**

  2. * Thread Safe Lazy Type Singleton

  3. *

  4. * @ThreadSafe

  5. *

  6. */

  7. class Singleton {

  8.    private static Singleton instance = null;

  9.    public static Singleton getInstance() {

  10.        synchronized (Singleton.class) {

  11.            if (instance == null) {

  12.                instance = new Singleton();

  13.            }

  14.        }

  15.    return instance;

  16.    }


  17.    // Other functions

  18. }

     上面的代码依然能够再次优化。因为懒汉式和饿汉式的区别在于对象实例在何时创建,当对象实例创建之后,我们不再需要进行同步了。

多线程的懒汉式:双检锁Double-Check Locking,简称DCL:

     特点:减少了懒汉式单例模式的线程同步次数。

  
  
  
  
  1. /**

  2. * Double Check Locking Singleton

  3. *

  4. * @ThreadSafe

  5. *

  6. */

  7. class Singleton {

  8.    private volatile static Singleton instance = null;

  9.    private Singleton() {}

  10.    public static Singleton getInstance() {

  11.        if (instance == null) {

  12.            synchronized (Singleton.class) {

  13.                if (instance == null) {

  14.                    instance = new Singleton();

  15.                }

  16.            }

  17.        }

  18.        return instance;

  19.    }


  20.    // Other functions

  21. }

之所以将instance声明为volatile变量,是为了防止Java即时编译器进行指令重排序,从而导致无法安全地使用DCL这里要注意,只能在JDK1.5或更高版本安全执行上述代码,因为volatile屏蔽指令重排序地语义在JDK1.5中才被完全修复。较低版本的JDK中即使使用volatile仍然存在重排序地问题(《深入理解Java虚拟机》Page326)。

IoDH(Initializationon Demand Holder)式单例模式:

     饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题。IoDH则是一种将饿汉式单例和懒汉式单例的优点结合的更好的单例实现方式。

     在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,代码如下:

  
  
  
  
  1. //Initialization on Demand Holder

  2. class Singleton {

  3.    private Singleton() {

  4. }

  5. private static class HolderClass {

  6.    private final static Singleton instance = new Singleton();

  7. }

  8. public static Singleton getInstance() {

  9.    return HolderClass.instance;

  10. }

  11. public static void main(String args[]) {

  12.        Singleton s1, s2;  

  13.        s1 = Singleton.getInstance();

  14.        s2 = Singleton.getInstance();

  15.        System.out.println(s1==s2);

  16.    }

  17. }

     由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

     通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式。

     IoDH也有缺点:IoDH的实现与编程语言本身的特性相关,很多面向对象语言不支持IoDH。

使用Enum来实现单例模式

《Effective Java》作者Joshua Bloch提出[第3条]可以用单元素枚举来实现Singleton。Joshua Bloch这么描述:“...单元素的枚举类型已经成为实现Singleton的最佳方法”。

coolxing的博客记载了这种方法的例子,以及这种方式如何完美抵御可能破坏单例性质的三种方式。

http://coolxing.iteye.com/blog/1446648


前面说明了如何用Java创建多线程安全并且延迟加载的Singleton模式,实现单例模式还需要注意下面几点(日后再分析):

1.是否可以通过反射获得新的单例对象?

2.是否可以通过clone方法获得新的单例对象?

3.是否可以通过序列化方式获得新的单例对象?





参考资料:

http://www.blogjava.net/Jhonney/archive/2011/04/08/110280.html

http://blog.csdn.net/lovelion/article/details/7420888


其他资料:

单例模式讨论篇:单例模式与垃圾回收http://blog.csdn.net/zhengzhb/article/details/7331354

http://www.chenyudong.com/archives/java-singleton.html

你可能感兴趣的:(设计模式,单例模式,DCL,双检锁,IoDH)