什么是单例模式?

一、单例模式介绍

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。对于代码开发中,一个类同时只有一个实例对象的情况就叫做单例。

二、那么,如何保证一个类只能有一个对象呢?

我们知道,在面向对象的思想中,通过类的构造函数可以创建对象,只要内存足够,可以创建任意个对象。所以,要想限制某一个类只有一个单例对象,就需要在他的构造函数上下功夫。

实现对象单例模式的思路是:
1、一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
2、当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
3、同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

下面实现了一个简单的单例模式。我们通过将构造方法定义为私有,然后提供一个getInstance方法,该方法中来判断是否已经存在该类的实例,如果存在直接返回。如果不存在则创建一个再返回。

package demo;

/**
 * @author czd
 */
public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

但是,上面的代码并没有考虑并发的情况,并不是一个线程安全的单例。如果有两个线程同时执行到if(instance==null)这行代码,这是判断都会通过,然后各自会执行instance = new Singleton();并各自返回一个instance,这时候就产生了多个实例,就没有保证单例!

上面的这种懒汉模式并不是线程安全的,所以并不建议在日常开发中使用。基于这种模式,我们可以实现一个线程安全的单例的,如下:

package demo;

/**
 * @author czd
 */
public class Singleton {
    private  static Singleton instance;

    private Singleton() {
    }

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

通过在getInstance方法上增加synchronized,通过锁来解决并发问题。这种实现方式就不会发生有多个对象被创建的问题了,但是,这种写法有一个很大的问题,那就是效率低

三、双重校验锁

上面这种线程安全的懒汉写法能够在多线程中很好的工作,但是,遗憾的是,这种做法效率很低,因为只有第一次初始化的时候才需要进行并发控制,大多数情况下是不需要同步的。我们其实可以把上述代码做一些优化的,因为懒汉模式中使用synchronized定义一个同步方法,我们知道,synchronized还可以用来定义同步代码块,而同步代码块的粒度要比同步方法小一些,从而效率就会高一些。如以下代码:

package demo;

/**
 * @author czd
 */
public class Singleton {
    private  volatile static Singleton instance;

    private Singleton() {
    }

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

上面这种形式,只有在singleton == null 的情况下再进行加锁创建对象,如果singleton!=null的话,就直接返回就行了,并没有进行并发控制。大大的提升了效率。从上面的代码中可以看到,其实整个过程中进行了两次singleton == null的判断,所以这种方法被称之为"双重校验锁"。

还有值得注意的是,双重校验锁的实现方式中,静态成员变量singleton必须通过volatile来修饰,保证其初始化的原子性,否则可能被引用到一个未初始化完成的对象。

四、饿汉模式
前面提到的懒汉模式,其实是一种Lazy-Loading思想的实践,这种实现有一个比较大的好处,就是只有真正用到的时候才创建,如果没被使用到,就一直不会被创建,这就避免了不必要的开销。但是这种做法,其实也有一个小缺点,就是第一次使用的时候,需要进行初始化操作,可能会有比较高的耗时。如果是已知某一个对象一定会使用到的话,其实可以采用一种饿汉的实现方式。

所谓饿汉,就是事先准备好,需要的时候直接给你就行了。这就是日常中比较常见的"先买票后上车",走正常的手续。

饿汉模式写法,如下所示:

package demo;

/**
 * @author czd
 */
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

上面的饿汉模式实现方法是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。

因为类的初始化是由ClassLoader完成的,这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)

除了上面的实现饿汉模式的方式,还有一种实现方式也是借助了calss的初始化来实现的,那就是通过静态内部类来实现的单例:

package demo;

/**
 * @author czd
 */
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

前面提到的饿汉模式,只要Singleton类被装载了,那么instance就会被实例化。

而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

使用静态内部类,借助了classloader来实现了线程安全,这与饿汉模式有着异曲同工之妙,但是他有兼顾了懒汉模式的Lazy-Loading功能,相比较之下,有很大优势。

你可能感兴趣的:(设计模式,单例模式讲解)