思维盛宴之设计模式-单例模式Singleton Pattern

来源:互联网 发布:剑三脸型数据是视频 编辑:程序博客网 时间:2024/05/01 22:32

1. 适用场景

单例模式可能是众多设计模式中相对最简单的一种了。它能够保证在一个jvm虚拟机中单例类只有一个实例。通常,在应用软件系统中,线程池,缓存,日志,工具或其他拥有管理功能的类都被实现为单例,因为他们在整个系统中只需要存在一份,也只需要对外提供一个全局的访问点即可。

单例的实现有三种方式:

  • 懒汉式
  • 饿汉式
  • 登记式

登记式的底层实现依然是懒汉式或饿汉式(将不同类的单例们存放在一个内部的Map中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,先登记,再返回),因此这里就不做讨论了。我们主要来看前两种,也是大家见得最多的两种。

2. 代码实现

2.1 懒汉式

public class Singleton {    private Singleton() {}    //全局单例    private static Singleton instance =null;    //静态方法    public static Singleton getInstance() {        if (instance == null) {             //(1)            instance = new Singleton();        }        return instance;    }}

上面的代码就是一个最简单的懒汉式单例实现。通过private访问修饰符限定构造器对外不可见,因此无法在外部手动创建一个Singleton实例,同时对外提供一个全局的访问点getInstance方法,以统一的控制对象的获取。这个获取过程在内部保证了对象只会创建一份。注意,这里的全局单例指的是在同一个jvm中的全局单例。

要指出的是,实际上即使构造函数被设置为private,java中的反射机制(如通过setAccessible方法设置方法可见并随后直接调用构造器)仍然可以对其进行调用并实例化一个单例类,这里对这种情况暂不做考虑。

上述的懒汉式单例实现在多线程并发的情况下是不安全的,因为多个线程可能同时执行到(1)处,发现不存在单例实例,于是各自构造器出一个实例,单例存在多个副本,状态不一致了,也就失去了其作用。

那么如果想要把上面的代码变成线程安全的,如何做呢?

有三种方法:

  1. getInstance方法上加同步

    public static synchronized Singleton getInstance() {    if (instance == null) {        instance = new Singleton();    }    return instance;}
  2. double check:双重判断

    //全局单例//使用volatile关键字来声明单例对象(new Singleton()是非原子操作)private static volatile Singleton instance = new Singleton();public static Singleton getInstance() {    if (instance == null) {        synchronized (Singleton.class) {            if (instance == null) { //双重检查防止再次new出实例                instance = new Singleton();            }        }    }    return instance;}
  3. 使用静态内部类改造

    private static class LazyHolder {    private static final Singleton INSTANCE = new Singleton();}public static final Singleton getInstance() {    return LazyHolder.INSTANCE;}

方法3既可以实现线程安全(类加载时已自行实例化INSTANCE),又能够避免同步带来的性能影响。

2.2 饿汉式

private Singleton() {}//全局单例private static Singleton instance = new Singleton();//静态方法public static Singleton getInstance() {    return instance;}

饿汉式与上面的方法3有点相似,在类初始化时就已经创建好一个静态的全局单例供系统使用,并且jvm的类加载机制保证了这个类只被加载一次,因此它也是线程安全的。

3. 思考与总结

饿汉式依靠jvm的类加载机制天生实现了线程安全,而懒汉式本身则是线程不安全的,使用上述的3种方法可以对其改造,其中第3种在性能上更优(因为避免了同步)。

通常,饿汉式的实现方式已经能够满足绝大多数需求,性能不错,代码也少,方便且易理解。

0 0
原创粉丝点击