单件模式(singleton pattern)

含义:用来创建独一无二的实例对象。

一、意图

单件模式(又称单例模式)确保一个类只有一个实例,并提供一个全局访问点。

二、适用性

1.当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
2.当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

三、结构

image.png


四、单件模式的经典实现

使用java实现单例模式如下:

  1. package com.pattern.singleton;
  2. public class Singleton
  3. {
  4. private static Singleton uniqueInstance = null;
  5. //其他有用的实例变量
  6. //构造方法是私有的,所以在类外不能new出多个实例
  7. private Singleton()
  8. {
  9. //初始化其他实例变量
  10. }
  11. public static Singleton getInstance()
  12. {
  13. if (uniqueInstance == null)
  14. {
  15. uniqueInstance = new Singleton();
  16. }
  17. return uniqueInstance;
  18. }
  19. }

只有在首次使用这个类的实例时才会产生这个“单例”,否则永远不会产生,这就是“延迟实例化(lazy instantiaze)”;

五、处理多线程

可以看出在多线程情况下,上述的getInstance方法可能会返回不同的实例(比如两个线程同时判断出uniqueInstance为null,接下来就会产生两个不同的实例),为了解决这种情况,可以使用以下方法:

1.使用synchronized关键字将getInstance()方法变成同步方法

    public static synchronized Singleton getInstance()
    {
        if (uniqueInstance == null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

使用synchronized可以迫使每个线程进入该方法之前,要要先等候其他线程离开该方法(synchronized锁定的是该类对应的Class对象),java提供的这种并发控制方法在此处可 能会使性能严重降低:只有第一次执行getInstance()方法时才需要同步,之后每次调用这个方法时,同步都会成为累赘(并行执行改为串行执行),若getInstance()需要被频繁 执行,则性能会大大降低。

2.使用“急切”创建实例,而不用延迟实例化

public class Singleton
{
    private static Singleton uniqueInstance = new Singleton();
    private Singleton()
    {
        //其他
    }
         //在JVM加载该类时已经创建此唯一的单件实例
    public static  Singleton getInstance()
    {
        return uniqueInstance;
    }
}

在静态初始化时创建单件,保证了线程安全。但是如果这个对象非常耗费资源,而在程序的执行过程中并没有使用到它,那就造成资源的浪费了。

3.用“双重检查加锁”,在getInstance()中减少使用同步

使用双重检查加锁(double-checked locking),首先检查实例是否已经创建了,如果尚未创建,才进行同步,这样一来既实现了延迟实例化又避免了多线程同步synchronized所产生的性能降低的问题,是一个不错的解决方法。

public class Singleton
{
    private volatile static  Singleton uniqueInstance = null;
    //其他

    private Singleton()
    {
        //其他
    }

    public static  Singleton getInstance()
    {
        if (uniqueInstance == null)
        {
            synchronized(Singleton.class)
            {
                if(uniqueInstance==null)
                {
                    uniqueInstance = new Singleton();
                }

            }
        }
        return uniqueInstance;
    }
}

volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量。
注意:双重检查加锁不适用于1.4及更早的版本,在1.4及更早版本中许多JVM对volatile关键字的实现会导致双重检查加锁的失效。

六、其他

1.很多情况如:线程池、日志对象、数据库连接等,我们需要一个对象或者需要控制实例的个数时,应当使用单例模式。

2.在java 1.2之前,垃圾收集器会造成当单件在没有全局的引用时被当做垃圾清除,这样在调用getInstance()方法时会产生一个新的实例,一切都回到最原始的设置,单例模式就没有意义了。所以必须建立单件注册表以免垃圾收集器将单件收回。

3.因为每个类加载器都有自己的命名空间,不同的类加载器可能会加载同一个类,所以在使用单件模式时多个个类加载器可能有机会各自创建自己的单件实例,解决办法:自行指定类加载器,并指定同一个类加载器。

4.最好不要继承单件类。因为必须把单件的构造器改为public或protected,就不是真正的单件了。

5.全局变量和单件模式:
在java中,全局变量基本上就是对对象的静态(static)引用(实际上java中并没有像C++中那样的全局变量的概念),如果将对象赋值给一个全局变量,那么就必须在程序一开始时就创建好对象而不是延迟实例化,可能会造成资源的浪费,而单件模式可以实现延迟实例化;全局变量可以提供全局访问但是不能确保只有一个实例,用许多全局变量指向许多小对象会造成命名空间(namespace)的污染,单件不鼓励这样的现象,但单件仍然可能被滥用。

转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8847672