一、什么是单例模式

       单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式一般体现在类声明中,单例的类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

二、单例模式的优缺点

   优点:

  1. 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了
  2. 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

   缺点:

  1.  单例模式没有抽象层,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  2.  单例类的职责过重,在一定程度上违背了“单一职责原则”。
  3.  滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
  4. 又比如:在多个线程中操作单例类的成员时,但单例中并没有对该成员进行线程互斥处理。

三、单例模式的使用场景

  •  网站的计数器,一般也是采用单例模式实现,否则难以同步。
  •  应用程序的日志应用,一般都可用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  •  Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  •  数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  • 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  • 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  • HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

总结以上,不难看出:

单例模式应用的场景一般发现在以下条件下:

(1)资源共享的情况下,避免由于资源操作时导致的性能问题或损耗等。如上述中的日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

四、单例模式的实现方式

1、饿汉式

饿汉式的写法通常静态成员变量已经是初始化好的,优点是可以不加锁就获取到对象实例,线程安全,主要的缺点在于不是延加载,稍微存在内存的浪费,因为如果初始化的逻辑较为复杂,比如存在网络请求或者一些复杂的逻辑在内,就会产生内存的浪费

饿汉式的写法通常静态成员变量已经是初始化好的,优点是可以不加锁就获取到对象实例,线程安全,主要的缺点在于不是延加载,稍微存在内存的浪费,因为如果初始化的逻辑较为复杂,比如存在网络请求或者一些复杂的逻辑在内,就会产生内存的浪费。

 

package Singleton;

/**
 * 单例模式 饿汉式
 * 声明静态时已经初始化,在获取对象之前就初始化
 *
 * 优点:获取对象的速度快,线程安全(因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的)
 *
 * 缺点:耗内存(若类中有静态方法,在调用静态方法的时候类就会被加载,类加载的时候就完成了单例的初始化,拖慢速度)
 *
**/
 public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    //私有化构造方法
    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

2、懒汉式

懒汉式的写法解决了饿汉式浪费内存的问题,在真正需要获取实例对象的才去执行初始化。

通常一般来说可能会有两种方式,第一种就是不加锁的写法,很显然这样是肯定不行的,正常的方式一般都是通过同步锁的方式加锁获取实例对象。

但是这种实现方式在之前的JDK版本synchronized没有锁优化的情况每次获取单例对象性能存在很大的问题,于是乎有了DCL的写法。

 

package Singleton;

/**
 * 单例模式 懒汉式  这种懒汉式是线程安全的
 * synchronized同步锁:多线程下保证单例对象唯一性
 *
 * 优点:单例只有在使用时才被实例化,一定程度上节约了资源
 *
 * 缺点:加入synchronized关键字,造成不必要的同步开销。不建议使用
 */
public class LazySingletonSafe {
    //本类内部创建
    private static LazySingletonSafe lazySingleton = null;
    //构造函数私有化
    private LazySingletonSafe(){}

    private static synchronized LazySingletonSafe getInstance(){
           if (lazySingleton == null){
               lazySingleton = new LazySingletonSafe();
            }
       return lazySingleton;
    }
}

 3 双重加锁验证DCL

于是为了解决懒汉式性能的问题,双重加锁验证的写法诞生了,先判断一次空,真的为空再执行加锁,然后再判断一次。

这样的话,只有在实例对象是空的情况才会去加锁创建对象,性能问题得到了一定程度上的解决,也不会和饿汉一样有内存浪费的问题。

 

package Singleton;

/**
 * 双重锁定体现在两次判空)
 * 优点:既能保证线程安全,且单例对象初始化后调用getInstance不进行同步锁,资源利用率高
 * 缺点:第一次加载稍慢,由于Java内存模型一些原因偶尔会失败,在高并发环境下也有一定的缺陷,但概率很小。
 */
public class SingletonByDoubleCheck {
    /**
     * 单例对象实例
     */
    private volatile static SingletonByDoubleCheck instance = null;//这里加volatitle是为了避免DCL失效

    //DCL对instance进行了两次null判断
    //第一层判断主要是为了避免不必要的同步
    //第二层的判断则是为了在null的情况下创建实例。
    public static SingletonByDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (SingletonByDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonByDoubleCheck();

                }
            }
        }
        return instance;
    }
}

4 静态内部类

这个通过JVM来保证创建单例对象的线程安全和唯一性,是比较好的办法。

Singleton类加载的时候,SingletonHolder不会加载,只有在调用getInstance方法的时候才会执行初始化,这样既起到了懒加载的作用,同时又使用到了JVM类加载机制,保证了单例对象初始化的线程安全。

这种方式也是目前比较推荐的一种方式。

 

package Singleton;

/**
 * 使用静态内部类来实现单例
 *由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
 * 所以这种形式,很好的避免了反射入侵。
 *
 * 优点:线程安全、保证单例对象唯一性,同时也延迟了单例的实例化,避免了反射入侵
 *
 * 缺点:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久代的对象。
 * (综合来看,私以为这种方式是最好的单例模式)
 */
public class SingletonByStaticInnerClass {

    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private final static SingletonByStaticInnerClass singletonByStaticInnerClass = new SingletonByStaticInnerClass();
    }

    //构造函数初始化
    private SingletonByStaticInnerClass(){}

    public SingletonByStaticInnerClass getInstance(){
        return SingletonHolder.singletonByStaticInnerClass;
    }
}

5 枚举

package Singleton;

/**
 * 枚举的方式实现单例
 */
public enum  EnumSingleton {
    ONE(1,"qd"),
    TWO(2,"bj");

    private int code;
    private String city;

    EnumSingleton(int code, String city) {
        this.code = code;
        this.city = city;
    }
}

最后这里推荐使用的是静态内部类的形式.