单例模式是最经常使用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。通常介绍单例模式的书籍,文章都会提到 饿汉式
和 懒汉式
这两种实现方式。可是除了这两种方式,实际上还有不少中方式。java
单例模式是一种经常使用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。数据库
许多时候整个系统只须要拥有一个的全局对象,这样有利于咱们协调系统总体的行为。好比在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,而后服务进程中的其余对象再经过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。设计模式
单例模式要求类可以有返回对象一个引用(永远是同一个)和一个得到该实例的方法(必须是可见的静态方法,一般使用getInstance这个名称)。安全
单例的实现主要是经过如下两个步骤:服务器
单例模式在多线程的应用场合下必须当心使用。若是当惟一实例还没有建立时,有两个线程同时调用建立方法,那么它们同时没有检测到惟一实例的存在,从而同时各自建立了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例惟一的原则。 多线程
解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会下降效率)。ide
//单例饿汉式实现 public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return INSTANCE; } } //测试方法 public static void main(String [] args){ //输出:true 单例正确,不然错误 System.out.println(Singleton.getInstance()==Singleton.getInstance()); }
优势:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。工具
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。若是从始至终从未使用过这个实例,则会形成内存的浪费。性能
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() {} public Singleton getInstance() { return instance; } }
这种方式和上面的方式其实相似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。测试
优缺点和上面是同样的。
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
这种写法起到了Lazy Loading的效果,可是只能在单线程下使用。若是在多线程下,一个线程进入了if (singleton == null)判断语句块,还将来得及往下执行,另外一个线程也经过了这个判断语句,这时便会产生多个实例。因此在多线程环境下不可以使用这种方式。
public class Singleton { private static Singleton singleton; private Singleton() {} public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
解决上面第三种实现方式的线程不安全问题,作个线程同步就能够了,因而就对getInstance()方法进行了线程同步。
缺点:效率过低了,每一个线程在想得到类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想得到该类实例,直接return就好了。方法进行同步效率过低要改进。
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
因为第四种实现方式同步效率过低,因此摒弃同步方法,改成同步产生实例化的的代码块。可是这种同步并不能起到线程同步的做用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还将来得及往下执行,另外一个线程也经过了这个判断语句,这时便会产生多个实例。
public class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { //双重检查 if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
Double-Check概念对于多线程开发者来讲不会陌生,如代码中所示,咱们进行了两次if (singleton == null)检查,这样就能够保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优势:线程安全;延迟加载;效率较高。
public class Singleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
这种方式跟饿汉式方式采用的机制相似,但又有不一样。二者都是采用了类装载的机制来保证初始化实例时只有一个线程。不一样的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的做用,而静态内部类方式在Singleton类被装载时并不会当即实例化,而是在须要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,因此在这里,JVM帮助咱们保证了线程的安全性,在类进行初始化时,别的线程是没法进入的。
优势:避免了线程不安全,延迟加载,效率高。
public enum Singleton { INSTANCE; public void method() { } }
借助JDK1.5中添加的枚举来实现单例模式。不只能避免多线程同步问题,并且还能防止反序列化从新建立新的对象, **防止经过反射进行单例*****。多是由于枚举在JDK1.5中才添加,因此在实际项目开发中,不多见人这么写过。
系统内存中该类只存在一个对象,节省了系统资源,对于一些须要频繁建立销毁的对象,使用单例模式能够提升系统性能。
当想实例化一个单例类的时候,必需要记住使用相应的获取对象的方法,而不是使用new,可能会给其它开发人员形成困扰,特别是看不到源码的时候。