【设计模式】单例模式

172 阅读5分钟

什么是单例模式

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。

简单点来说:一个类只能被创建一个实例对象,这样的类被称为单例类,这种设计模式就是单例模式。

为什么要使用单例模式

1.多线程中保证线程安全。

2.可以表示全局唯一。

如何实现一个单例模式

这里我主要介绍4中创建方式,但是需要注意一下集中情况:

  • 构造器必须是私有的(private),防止被外部new,这样单例就不存在了。
  • 单例是否需要延迟加载(也就是:运行时创建还是使用时创建)。
  • 考虑线程的安全问题。
  • *获取实例的时候是否需要加锁。

饿汉式的实现非常简单,就是在程序启动类加载时,将对象实例化,然后通过方法暴露给调用方,这种方式属于线程安全,以为没有存在竞争关系,但是有一个缺陷,那就是不支持延迟加载,也就是说必须在类加载的时候创建实例,无法按需加载,代码实现如下:

package com.ymy.mode;

/**
 * 单例模式(饿汉)
 */
public class HungrySingleton {

    //初始化实例
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    /**
     * 无参构造
     * 必须要指定为私有的(private),防止new创建实例
     */
    private HungrySingleton() {
    }

    /**
     * 给调用方暴露的方法,获取实例
     * @return
     */
    public static HungrySingleton getInstance(){

        return hungrySingleton;

    }
}

这种方式就是在类加载的时候将对象实例化,这时候你会发现,我都还没有使用,为什么就已经将实例创建出来了呢?这样会拖慢启动的时间,而且该实例一直会占用着资源,提前创建就会浪费不要要的资源,那这种创建方式是不是就没有任何意义了呢?我觉得他还是有一定的意义存在。

1.保证线程安全问题。

2.如果一个类的初始化时间很长,那么我建议使用饿汉式,理由:当一个访问请求需要创建实例的时候,由于初始化实例需要很长的时间,这时候就会导致请求超时。

3.如果实例占用的资源较多,这样也会造成一个问题:OOM,按照 fail-fast 的设计原则(有问题及早暴露),我希望在程序启动的时候就能知道这个对象是否会造成资源压力,而不是在程序运行了一段时间以后。

懒汉式:对比饿汉式他具有了延迟加载功能,也就是按需加载,实现代码如下:

package com.ymy.mode;

/**
 * 单例模式(懒汉)
 */
public class IdlerSingleton {

    private  volatile static IdlerSingleton idlerSingleton;
    /**
     * 无参构造
     * 必须要指定为私有的(private),防止new创建实例
     */
    private IdlerSingleton() {
    }

    /**
     * 给调用方暴露的方法,获取实例
     * @return
     */
    public static synchronized IdlerSingleton getInstance(){
        if(null == idlerSingleton){
            return new IdlerSingleton();
        }
        return idlerSingleton;

    }
}

getInstance():在此方法中创建实例,创建之前做判断,如果已经创建过了,直接返回,否者 new一个新的实例。

这种创建方式需要注意一下几点:

1.getInstance()方法可能会存在线程安全问题,需要考虑是否加锁。

2.无参构造为私有

3.成员变量idlerSingleton为静态变量,最好使用volatile关键字修饰,理由:因为在多线程安全问题中有一个问题就是有序性的问题,虽然jdk在高版本修复了这个问题,建议最好加上volatile,禁止指令重排序。

在上面的代码中,我们在getInstance()加了synchronized来保证线程安全问题,让获取实例变成串行化,如果获取实例的操作过于频繁,那么程序就会频繁的加锁/释放锁,大大降低了程序的效率,如果只是偶尔使用,那么这种创建方式还是不错的。

我们介绍的前面两种都有各自的缺点,饿汉式不支持延迟加载,消耗资源,懒汉式存在性能问题,那么有没有一种方式可以解决这两个问题呢?、

答案是肯定的,那就是这里索要说的第三种创建方式:双重检测,实现也很简单,在getInstance()方法中先判断实例是否为空,如果不为空,直接返回对象,否者加锁,在判断实例是否为空,为空则new对象返回,代码实现如下:

package com.ymy.mode;

/**
 * 单例模式(懒汉)
 */
public class IdlerSingleton {

    private  volatile static IdlerSingleton idlerSingleton;
    /**
     * 无参构造
     * 必须要指定为私有的(private),防止new创建实例
     */
    private IdlerSingleton() {
    }

    /**
     * 给调用方暴露的方法,获取实例
     * @return
     */
    public static  IdlerSingleton getInstance(){
        if(null == idlerSingleton){
            synchronized (IdlerSingleton.class){
                if(null == idlerSingleton){
                    return new IdlerSingleton();
                }
            }
        }
        return idlerSingleton;

    }
}

这里需要注意一点,那就是加锁的对象,锁对象使用不当将无效果。

这是一种更简单的实现方式,比前面三种都简单,它同样支持延迟加载,我们来看具体实现:

package com.ymy.mode;

/**
 * 单例模式(懒汉)
 */
public class IdlerSingleton {

    /**
     * 无参构造
     * 必须要指定为私有的(private),防止new创建实例
     */
    private IdlerSingleton() {
    }

    /**
     * 静态内部类
     */
    private static class SingletonInterior{
        private  volatile static IdlerSingleton idlerSingleton = new IdlerSingleton();
    }

    /**
     * 给调用方暴露的方法,获取实例
     * @return
     */
    public static  IdlerSingleton getInstance(){
        return SingletonInterior.idlerSingleton;
    }
}

SingletonInterior是一个静态内部类,外部类IdlerSingleton加载时并不会加载SingletonInterior,那什么时候会加载它呢?,那就是当getInstance()方法被调用的时候,内部类就会被加载,这种方式同样实现了延迟加载效果,实现方式也比上面几种简单了很多,这里的线程安全问题以及实例的唯一性都是JVM保证的,无需我们开发者多虑。

最后还有一种枚举的实现方式,这里我就不做代码展示了,感兴趣的可以自己研究一下。