单例模式

来源:互联网 发布:淘宝怎么好友代付 编辑:程序博客网 时间:2024/05/01 09:19

本篇博客主要记录一下单例模式。


单例模式大家应该都不陌生,主要用于创建一个“独一无二”的对象。
它基本的写法类似于:

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

容易看出,单例模式其实就是利用一个静态变量uniqueInstance来记录Singleton的唯一实例。

Singleton的构造器被声明为私有的,因此只有内部方法可以调用。

当客户端代码试图获取单例对象时,必须调用其静态方法getInstance。
在getInstance方法中,将判断Singleton是否被实例化过。
如果实例化过,则直接返回uniqueInstance;否则,将先实例化单例对象。

通过这种方式,外部类不能随意地实例化Singleton;
仅能通过Singleton提供的接口,获取对象。
而Singleton又管制了自身的创建,于是最终保证Singleton“独一无二”。


单例模式的定义和结构图都很简单,如下所示:
单例模式确保一个类只有一个实例,并提供一个全局访问点。

如图所示,单例模式其实就只有一个类,
它主要是通过上文中代码的固定“套路”来保证对象的“独一无二”。


需要注意的是,在多线程的场景下,上述单例的实现方式,将会产生问题。

例如,在初次初始化时,两个线程同时进入到getInstance方法,
并判断uniqueInstance为null,此时将会初始化多个实例。

针对这个问题,在多线程场景下,主要有三种解决方案。

1、同步getInstance方法
按照这种方案修改后的代码类似于:

public class Singleton {    private static Singleton uniqueInstance;    private Singleton() {}    //增加synchronized关键字    //迫使每个线程在进入该方法前,必须等待其它线程离开    public static synchronized Singleton getInstance() {        if (uniqueInstance == null) {            uniqueInstance = new Singleton();        }        return uniqueInstance;    }}

这种修改方案比较简单,唯一的缺点是对性能有影响。
如果getInstance将被频繁调用,那么就不能这么改。

2、由JVM初始化类时创建单例
按照这种方案修改后的代码类似于:

public class Singleton {    //类被加载时,就创建出单例    //保证了线程安全    private static Singleton uniqueInstance = new Singleton();    private Singleton() {}    public static Singleton getInstance() {        return uniqueInstance;    }}

一般单例被频繁使用,或创建和运行的负担不重时,可以采取这种方式。

3、利用“double checked locking”来同步getInstance方法

public class Singleton {    //这里注意使用volatile关键字    //保证一个线程创建单例后,另一个线程能够发现这种改变    private volatile static Singleton uniqueInstance;    private Singleton() {}    public static Singleton getInstance() {        if (uniqueInstance == null) {            //只有第一次创建时,才会真正利用synchronized进行同步            //因此对性能的影响较小            synchronized (Singleton.class) {                //进入同步块后,再次判断是否创建                if (uniqueInstance == null) {                    uniqueInstance = new Singleton();                }            }        }        return uniqueInstance;    }}

这种方式可以看作对第1种方式的性能优化。


最后,需要说明的是:
每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,
不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。
如果这种事情发生在单例上,就会产生多个单例并存的现象。

因此,如果程序中有多个类加载器又使用了单例模式,
那么最后指定同一个类加载器加载单例类。

0 0